§13 Символьный тип. Строки. Контейнер string. Локализация

Символьный тип данных

В C++ символьный тип данных char относится к целым типам, так как в переменных этого типа скрыт код символа в кодовой таблице (ASCII). Размер памяти отводимой на хранение объекта типа char равен 1 байт. Символьные литералы (один или два символа) должны заключаться в апострофы (одинарные кавычки):

char ch_1 = 'F';
char ch_2 = '\n';

Использование двойных кавычек приведет к ошибке.
Заметим, однако, что если в консоли используется кодировка UTF-8, то корректно обрабатываться будут только символы первой части таблицы ASCII (“аски”), поскольку она входит в таблицу кодировки UTF-8 без изменений, включая размер отводимый для хранения символа, т. е. 1 байт. Во время компоновки программы ниже компилятор выдаст предупреждение, что ‘Б’: “многознаковая символьная константа [-Wmultichar]” и “переполнение при неявном преобразовании константы [-Woverflow]”.

char ascii = 'W';
char non_ascii = 'Б';
cout << ascii     << " => " << sizeof(ascii)
     << non_ascii << " => " << sizeof(non_ascii)
     << endl;

Это означает, что символ 'Б' не является однобайтовым. Запустить программу, конечно можно, но символ 'Б' не будет обработан корректно. Чтобы решить данную проблему нужно включить локализацию и использовать для работы с символами специальный тип широких символов wchar_t. Как это сделать мы обсудим ниже.
Для того, чтобы получить символ с клавиатуры можно использовать метод класса istream - get():

int main()
{
    char a;
    while ((a = cin.get()) != '\n') {
        cout << a << endl;
    }
    cout << "a => "; cin.get(a);
    cout << a << endl;
    return 0;
}

Поскольку, как мы уже сказали, тип char является целочисленным, то нередко можно встретить такой трюк для преобразования символ => число:

char cr = '7';
int s = '7' - '0';
cout << cr << " = " << s << endl;

Объясняется это просто. Код символа '0' равен 48, а код '7' равен 55. Разница кодов дает цифру 7, которая неявно преобразуется к типу int.
Вывод символов по их кодам можно осуществить следующим образом:
Программа 13.1

#include <iostream>
using namespace std;

int main() {
	for (int i = 33; i < 127; i++)
            cout << i << " => " << char(i) << endl;
	return 0;
}

В этой программе используется явное преобразование типов.
Специальными символьными литералами являются управляющие последовательности. Они используются для описания определённых специальных символов внутри строковых литералов.
Некоторые управляющие последовательности:

\'	одинарная кавычка
\"	двойная кавычка
\?	вопросительный знак
\\	обратный слеш
\0	нулевой символ
\n	перевод строки - новая строка
\t	горизонтальная табуляция
\v	вертикальная табуляция

Примечание. Мы уже применяли символ '\n' для перевода на новую строку при выводе матриц. Манипулятор endl, также осуществляет перевод на новую строку, но, в тоже время, производит сброс буфера вывода (а следовательно и всех форматов при использовании манипуляторов). Поэтому, там где нужно производить переход на новую строку и при этом сохранять форматы, нужно использовать '\n'.

C-строка как символьный массив

C-строка в С++ - это массив символов, в котором последний элемент имеет значение '\0' (нулевой символ).
Примечание. Не путать с символом '0'. В кодовой таблице ASCII нулевой символ (иначе NULL) находится на самой первой позиции, а именно нулевой, откуда и название.
Это значение является признаком конца строки. Символьный массив не является строкой, если он не заканчивается нулевым символом.
Способ инициализации C-строки, как массива не является удобным:

char str1[] {'T', 'h', 'i', 's', ' ', 's', 't', 'r', 'i', 'n', 'g', '!', '\0'};

Инициализировать C-строку можно более простым способом, заключив её в двойные кавычки:

char str2[] = "This string!";

В этом случае добавлять нулевой символ не нужно. Он будет добавлен неявно автоматически. При вводе символьного массива с клавиатуры нулевой символ также будет добавлен автоматически.
Для получения С-строки можно спользовать поток cin, например, так:

const int buf = 80;
char myStr[buf];
cout << "Введите строку: ";
cin >> myStr;

В этом фрагменте константой buf определяется размер массива (с учетом нулевого символа). Существует возможность ограничить количество вводимых символов с помощью манипулятора setw():

cin >> setw(buf) >> myStr;

В поток будут поступать символы, пока не будет встречен пробельный символ (к ним относятся следующие: собственно пробел, \t, \v, \r, \n и \f). В программе пробельный символ будет расцениваться как нулевой символ (т. е. конец строки) и в символьный массив будет записана только часть строки, до первого пробела, а все остальное будет оставлено на очереди в потоке.
Программа 13.2

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	const int buf = 80;
	char myStr[buf];
	cout << "Введите строку: ";
	cin >> setw(buf) >> myStr;
	cout << "Вы ввели строку:\n"
	     << myStr
	     << endl;
	return 0;
}
Введите строку: Это моя первая строка
Вы ввели строку: 
Это

Ввод и вывод C-строки. Функции get() и getline()

Но что, если необходимо взять не первое слово, а всю строку целиком? Для чтения строки, имеющей символы пробела, необходимо использовать функцию get(). Эта функция является методом класса istream, поэтому вызывается как функция-член этого класса: cin.get(). Исправим программу 13.2:

const int buf = 80;
char myStr[buf];
cout << "Введите строку: ";
cin.get(str, buf);
cout << "Вы ввели строку:\n"
     << myStr
     << endl;

Примечание. При использовании операции вставки "<<" и имени массива, С-строка выводится так, как если бы мы использовали переменную строки, т. е. при выводе строки не нужно прибегать к посимвольному выводу в цикле, как это делается для C-массивов.
Функция get() читает строку целиком, включая завершающий символ, но оставляет его в потоке. Завершающий символ (перевода на новую строку) заменяется нулевым символом. Функция get() имеет два аргумента: имя символьного массива и его размер. Если функция используется без аргументов, то произойдет считывание символа и удаление его из потока.
Примечание. Обычно функция get() используется без аргументов, если необходимо удалить в очереди потока завершающий символ.
Альтернативой функции get() является функция getline(), которая проще в использовании:

cin.getline(str, buf, char_delim);

Функция имеет три аргумента. Два обязательных: имя массива и размер передаваемого массива с учетом символа конца строки. Третий аргумент (не обязательно) используется для указания завершающего символа. Если третьего аргумента нет, то предполагается, что завершающий символ — '\0'. Функция считывает все символы пока не встретит завершающий символ (или '\n', если завершающий символ не указан в качестве третьего аргумента) или buf символов (с учетом добавления завершающего символа), если количество вводимых символов строки больше, чем указано в buf. Далее функция считывает завершающий символ, но в потоке ввода его не оставляет (также будут удалены и все символы превышающие лимит buf). Если быть точнее, то считывается символ перевода на новую строку, который заменяется на завершающий символ '\0' или char_delim.

Создание объекта класса string и его инициализация

В C++ С-строка имеет тип const char*. Это означает, что C-строка является массивом фиксированного размера. Для работы с C-строкой STD предоставляет большой набор функций. Но, тем не менее, работа с C-строкой является небезопасной и неудобной.
Стандартная библиотека языка С++ включает класс последовательного контейнера string, предназначенного для работы с массивами символов. Этот класс более безопасен в использовании, чем С-строка. Инструментарий класса string более чем достаточен, чтобы не прибегать к функциям, работающим со строкой в стиле Си. Вместе с тем, функции класса могут работать не только с экземплярами класса, но и с C-строками. C-строки автоматически преобразуются в строки типа string. (Обратное преобразование не производится, но есть возможность копировать символы в символьные C-массивы). В отличие от C-строки, контейнер string представляет строку как динамический массив. В строках типа string завершающий символ не добавляется в массив, но может содержаться в массиве, как и любой другой символ. Для работы с контейнером string стандартная библиотека предоставляет полный набор функций, общий для всех контейнеров. Для начала работы с классом string необходимо подключить заголовочный файл директивой:

#include <string>

Объявление переменных типа string (на языке ООП - создание экземпляра класса) не отличается от объявления переменных других типов:

string myStr;

Здесь создается пустая строка myStr.
Инициализировать объекты класса string можно различными способами.

//Инициализация во время объявления
string myStr("Это первая строка.");

//Объявление с присваиванием
string myStr = "Это вторая строка.";

//Копирование другой строки (копирующий конструктор);
//othe_Str может быть C-строкой
string myStr(other_Str);

//Определённой длины count C-строки
string myStr(chart_Str, count);

//Заполнить символами
string myStr(20, 'a');

//Копировать из диапазона [pos, pos+count)
string myStr(other_Str, pos, count);

//Создание строки с содержимым диапазона [first, last),
//где first и last - InputIterator
string myStr(first, last);

Ниже демонстрируется использование некоторых способов инициализации.
Программа 13.3

#include <iostream>
#include <string>
using namespace std;

int main() {
	char S1[] = "Aliena vitia in oculis habemus, a tergo nostra sunt";
	//Получаем объект string
	string S2(S1, 13, 9);
	cout << "S2 -> " << S2 << endl;
	string S3(S1);
	// Получаем итераторы для S3;
	auto first = S3.begin();
	auto last = S3.end();
	string S4(first + 13, last - 4);
	cout << "S4 -> " << S4 << endl;
	return 0;
}
S2 -> in oculis
S4 -> in oculis habemus, a tergo nostra 

Примечание. Отдельным символом строку инициализировать нельзя!

Ввод и вывод объектов класса string

Ввод и вывод строки типа string с помощью потоковых объектов cin и cout будет производиться абсолютно аналогично вводу/выводу C-строки. Для ввода строки типа string полностью, включая пробельные символы, необходимо использовать функцию getline. Это не тот метод класса istream, который мы применяли для C-строки, а глобальная функция класса string, с помощью которой можно получить объект этого класса.
Синтаксис этой функции таков:

getline (istream& is, string& myStr, char_delim);

Аргументы функции:
is - потоковый объект ввода;
myStr - объект класса string, куда будет помещена строка;
char_delim - третий аргумент (необязательный) используется для указания завершающего символа. Если третьего аргумента нет, то предполагается, что завершающий символ — '\n'.
Функция считывает строку полностью с завершающим символом. Сам символ новой строки отбрасывается и в строковой переменной не сохраняется. Также не добавляется нулевой символ. Символ новой строки завершает выполнение функции, где бы он не находился.
Если нулевой символ находится в начале строки, то функция вернет пустую строку. Рассмотрим этот случай:
Программа 13.4

#include <iostream>
#include <string>
using namespace std;

int main() {
	string S1;
	string S2;
	cout << "Введите строку S1\n";
	getline(cin, S1);
	cout << "S1 => " << S1 << endl;
	int i;
	cout << "i = "; cin >> i;
	cout << "Введите строку S2\n";
	//cin.ignore()
	getline(cin, S2);
	cout << i << "\n"
         << "S2 => " << S2 << endl;
	return 0;
}
Введите строку S1
This is my string
S1 => This is my string
i = 5
Введите строку S2
5
S2 => 

После выполнения инструкции в стр. 9: getline(cin, S2); и вывода числа программа выводит пустую строку и завершает свою работу. Причина того, что getline не приняла новую строку в том, что поток ввода оказался не пуст! После ввода значения переменной i в потоке остается завершающий символ, этот символ и перехватила функция getline. Для очистки потока можно применить функцию cin.get() или cin.ignore() (убрать в программе комментарий).

Локализация. Строки широких символов

Для работы с кодировками символов, которые превышают 8 бит (более "широких") предусмотрен специальный символьный тип wchar_t (wide character). Размер этого типа определяется компилятором и платформой. Так в Windows API wchar_t имеет фиксированный размер 16 бит (что не позволяет закодировать весь набор символов Unicode), а в GNU/Linux - 32 бита.
Прежде чем использовать строку широких символов, необходимо установить "локаль" определяющую в какой кодировке широкий символ будет передаваться в потоки и какой, при этом, национальный стандарт будет использован для форматирования. Для этого необходимо включить директиву:

#include <locale>

В самой программе необходимо создать глобальный объект локали, который соответствует локали окружения (кодировка и настройки используемые в ОС). Функции-члену locale() передается строка - имя локали (для Linux это ru_RU.UTF-8), как аргумент:

locale::global(locale("ru_RU.UTF-8"));

Для работы с широкими символами существуют специальные версии типов и классов, которые используются с префиксом "w": wstring, wcout, wcin и т. п. Строка-литерал широких символов должна иметь суффикс "L". Рассмотрим пример использования строки широких символов.
Программа 13.5 Дана строка G1, содержащая как русские, так и английские буквы и символ G2. Подсчитать общее количество символов, равных G2 в строке G1. Если таких символов нет, то сообщить об этом. Получить новую строку символы которой записаны в обратном порядке.

#include <iostream>
#include <string>
#include <locale>
#include <iterator>
#include <algorithm>
using namespace std;

int main() {
    locale::global(locale("ru_RU.UTF-8"));
    wstring G1;
    wchar_t G2;
    wcout << L"Введите строку G1:" << endl;
    getline(wcin, G1);
    wcout << L"Введите символ G2:" << endl;
    wcin >> G2;
    int i = 0;
    for (auto &s : G1) {
        if (s == G2) i++;
    }
    reverse(G1.begin(), G1.end());
    if (G1.find(G2) == string::npos)
        wcout << L"Такого символа нет!" << endl;
    else
        wcout << L"В строке найдено " << i
              << L" символов \"" << G2 << "\"" << endl;
    wcout << G1 << endl;
    return 0;
}
Введите строку G1:
Это строка содержащая разные simvoli :))
Введите символ G2:
о
В строке найдено 3 символов о
)): ilovmis еынзар яащажредос акортс отЭ

Примечание. Метод find находит первую подстроку (в данном случае символа) в данной строке и возвращает позицию первого элемента подстроки. npos является константой класса string. Смысл выражения string::npos, в данном случае, означает признак конца строки. Подробно о методах класса string мы будем говорить позднее.


Print Friendly, PDF & Email

Comments are closed