§14 Объект класса string как динамический массив. Посимвольный анализ строки. Числовые преобразования

Объект класса string как динамический массив

Объект класса string является динамическим массивом. Это означает, что для хранения добавляемых элементов необходимо увеличивать (или уменьшать) размер памяти, отводимой под хранение модифицированной строки. Иначе, эти процессы называются выделением памяти и освобождением памяти. Эту работу берет на себя класс string. Для контейнеров, которые содержат динамически изменяемые структуры данных, существуют два понятия размер и объем. Смысл этих понятий следующий. При создании объекта класса string в памяти резервируется некоторое адресное пространство, даже если начальный размер массива нулевой, т. е. строка не инициализирована. Размер массива определяется количеством символов, хранящихся в массиве. Размер можно получить с помощью метода size(). При добавлении элементов, объем массива (т. е. выделенная память для хранения элементов) может увеличится автоматически. Это приведет к процессу перераспределения памяти во время которого все элементы массива будут перенесены в новую область памяти. Для предотвращения этого процесса, настоятельно рекомендуется резервировать емкость строки методом reserve(). (Это, не означает запрет на увеличение или уменьшение массива. При необходимости, память может быть увеличена автоматически, если количество элементов станет больше). Для того, чтобы узнать какое количество символов может принять контейнер без увеличения объема, используется метод capacity(). Существует необязательный (для выполнения классом string) метод shrink_to_fit(), который запрашивает уменьшение объема capacity до size.

Методы push_back() и pop_back()

Контейнер string устроен так, что вставка и удаление символа в конце массива производится очень быстро. Однако, операции вставки в произвольную позицию являются мало эффективными, с точки зрения скорости работы программы. Но это неизбежная плата за работу со строкой любого типа будь-то C-строка или строка типа string (этот тезис не должен распространяться на другие контейнеры). Дело в том, что классу приходится выполнять работу по сдвигу массива, а заодно, если потребуется, то и работу по выделению памяти, чтобы принять новые элементы. А это, зачастую, приводит к процессу перераспределения памяти.
Для вставки и удаления символов в конце массива используются методы push_back() и pop_back()

push_back

Этот метод добавляет переданный символ к концу строки. Это позволяет произвести “сборку строки” по определенным критериям. Рассмотрим задачу.
Программа 14.1 Дано четное число N (> 0) и символы C1 и C2. Вывести строку длины N, которая состоит из чередующихся символов C1 и C2, начиная с C1.

#include <iostream>
using namespace std;

int main() {
	char C1, C2;
	int N;
	string S; // пустая строка
	cout << "C1 = "; cin >> C1;
	cout << "C2 = "; cin >> C2;
	cout << "N  = "; cin >> N;
	for (int i = 0; i < N; i++) {
		if (i & 1)
			S.push_back(C2);
		else
			S.push_back(C1);
	}
	cout << S << endl;
	return 0;
}
C1 = a
C2 = b
N  = 20
abababababababababab

Примечание. Операции конкатенации ("+" и "+=", которые будут рассмотрены на следующем уроке) могут выступать как альтернатива использованию этого метода.

pop_back

Этот метод, напротив, удаляет элемент в конце строки.

Лексикографическое сравнение

Для сравнения строк можно использовать обычные операции сравнения: ==, !=, <, <=, > и >=. В качестве операндов в операциях сравнения могут выступать объекты типа string, C-строки и строковые литералы. Лексикографический порядок можно представить в виде следующей последовательности: А < АА < ААА < ААБ < ААВ < АБ < Б < … < ЯЯЯ. Вначале сравниваются первые элементы, если они равны, то сравниваются вторые, затем третьи и т. д. Например:
Программа 14.2

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

int main() {
	string str1, str2;
	str1.reserve(10); // не обязательно
	str2.reserve(10);
	cout << "Введите строку 1:\n";
	getline(cin, str1);
	cout << "Введите строку 2:\n";
	getline(cin, str2);
	cout.setf(ios::boolalpha);
	cout << str1 << " > " <<  str2 << " = ";
	if (str1 > str2)
		cout << true << endl;
	else
		cout << false << endl;
	return 0;
}
Введите строку 1:
moon
Введите строку 2:
sun
moon > sun = false

Посимвольная обработка строки

Для доступа к отдельному символу строки применяются либо операция обращения по индексу [] (например, myStr[i], как в C-массивах), либо метод at() (например, myStr.at(i)). При этом используется обычный цикл for. Если не предвидится изменение размера строки, то лучшим выбором будет не обычный for, а range-based for (программа 14.3). Метод at() осуществляет контроль выхода за границы массива и если это произойдет, то будет сгенерировано исключение out_of_range. Следовательно, этому методу нужно отдавать предпочтение использованию операции обращения по индексу [].
Фрагмент программы в которой производится обход символьного массива с помощью традиционного for и range-based for:

string S;
getline(cin, S);
int k = 0, m = 0;
for (size_t i = 0; i < S.size(); i++)
    if (S.at(i) == '\"') k++;
for (auto &r : S)
    if (r == '\?') m++;

Примечание. Напоминаем, что двойная кавычка и вопросительный знак являются специальными символами, которые должны экранироваться символом '\' (управляющие последовательности).
Диапазонный цикл for (range-based for) нельзя использовать для массивов, которые, в процессе обработки, изменяют свой размер. Это объясняется тем, что этот вид цикла скрывает в себе работу с итераторами, а, как известно, при изменении размера массива итератор конца становится недействительным.
Если используется заголовочный файл cctype (или cwctype для широких символов) то становятся доступными функции для работы с отдельными символами строки.

  • isalnum()
    iswalnum() проверяет, является ли символ буквенно-цифровым. Например: if (isalnum(r)) {...}
  • isalpha()
    iswalpha() проверяет, является ли символ буквенным
  • isdigit()
    iswdigit() проверяет, является ли символ цифрой
  • isxdigit()
    iswxdigit() проверяет, является ли символ шестнадцатеричной цифрой
  • iscntrl()
    iswcntrl() проверяет, является ли символ управляющим символом
  • isspace()
    iswspace() проверяет, является ли символ символом пробела
  • islower()
    iswlower() проверяет, является ли символ символом в нижнем регистре
  • isupper()
    iswupper() проверяет, является ли символ символом прописной буквы
  • ispunct()
    iswpunct() проверяет, является ли символ символом пунктуации
  • tolower()
    towlower() преобразует символ в нижний регистр. Например: r = tolower(r)
  • toupper()
    towupper() преобразует символ в верхний регистр

Примечание. Аргументами функций библиотеки cwctype должны быть широкие символы (wchar_t)!
Программа 14.3

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

int main() {
	int i = 0;
	string myStr;
	cout << "Введите строку латинских символов:\n";
	getline(cin, myStr);
	for (auto &r : myStr)
		if (isupper(r)) i++;
	cout << "В строке " << i << " прописных букв" << endl;
	return 0;
}
Введите строку:
Repetitio Est Mater Studiorum
В строке 4 прописных букв

Unicode и кодировка символов UTF-8

Unicode — это стандарт кодирования символов, позволяющий представить знаки почти всех письменных языков (как используемых, так и исторических). Юникод имеет несколько форм представления (англ. Unicode transformation format, UTF): UTF-8, UTF-16 и UTF-32. В грядущем стандарте C++17 ожидается переход на кодировку utf-8 для кодирования строковых литералов. Кодировка utf-8 является одной из общепринятых и стандартизированных кодировок текста, которая позволяет хранить символы Юникода, используя переменное количество байт (от 1 до 6). Кодировка нашла широкое применение в UNIX-подобных операционных системах и в Интернете. Формат UTF-8 был изобретён 2 сентября 1992 года Кеном Томпсоном и Робом Пайком. Его преимущества перед другими кодировками в том, что в utf-8 осуществляется полная совместимость с символами таблицы ASCII имеющими десятичные коды 0 - 127, причем на эти символы отводится 8 бит (также как и в ASCII). В строках, где преобладает латиница, знаки пунктуации и пробел, использование кодировки utf-8 дает значительное сокращение информационного объема текста (в отличие от UTF-16, в MS Windows, в которой используется либо 16, либо 32 бита).
Начиная с версии Юникода 5.1 для кириллицы выделено четыре раздела в девятом блоке. Современный алфавит находится в первом разделе (и повторяет кодировку ISO 8859-5). Диапазон десятичных кодов: от 1040 (прописная буква 'А') до 1103 (строчная буква 'я'). Следующая программа может вывести весь кириллический сегмент Unicode:
Программа 14.4

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

int main() {
	locale::global(locale("ru_RU.UTF-8"));
	int i = 0;
	for (int i = 1024; i < 1279; i++) {
		wcout << wchar_t(i) << " ";
		if (i % 31 == 0) 
			wcout << endl;
	}
	return 0;
}
Ѐ Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н О 
П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н 
о п р с т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ 
ѝ ў џ Ѡ ѡ Ѣ ѣ Ѥ ѥ Ѧ ѧ Ѩ ѩ Ѫ ѫ Ѭ ѭ Ѯ ѯ Ѱ ѱ Ѳ ѳ Ѵ ѵ Ѷ ѷ Ѹ ѹ Ѻ ѻ 
Ѽ ѽ Ѿ ѿ Ҁ ҁ ҂ ҃ ҄ ҅ ҆ ҇ ҈ ҉ Ҋ ҋ Ҍ ҍ Ҏ ҏ Ґ ґ Ғ ғ Ҕ ҕ Җ җ Ҙ ҙ Қ 
қ Ҝ ҝ Ҟ ҟ Ҡ ҡ Ң ң Ҥ ҥ Ҧ ҧ Ҩ ҩ Ҫ ҫ Ҭ ҭ Ү ү Ұ ұ Ҳ ҳ Ҵ ҵ Ҷ ҷ Ҹ ҹ 
Һ һ Ҽ ҽ Ҿ ҿ Ӏ Ӂ ӂ Ӄ ӄ Ӆ ӆ Ӈ ӈ Ӊ ӊ Ӌ ӌ Ӎ ӎ ӏ Ӑ ӑ Ӓ ӓ Ӕ ӕ Ӗ ӗ Ә 
ә Ӛ ӛ Ӝ ӝ Ӟ ӟ Ӡ ӡ Ӣ ӣ Ӥ ӥ Ӧ ӧ Ө ө Ӫ ӫ Ӭ ӭ Ӯ ӯ Ӱ ӱ Ӳ ӳ Ӵ ӵ Ӷ ӷ 
Ӹ ӹ Ӻ ӻ Ӽ ӽ Ӿ 

Примечание. Обратите внимание на то, что для преобразования используется специальный тип широких символов wchar_t.
Поскольку порядок следования прописных и строчных букв совпадает, это может быть использовано для составления собственных простых функций преобразования из строчного символа в прописной (или наоборот) и проверки регистра символа, так как известно, что всего букв 32 (не считая ё, которую нужно обработать отдельно).
Программа 14.5 Составить программу в которой используются функции повышения регистра и проверки регистра символов строки, содержащей буквы русского алфавита.

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

void myToUpper(wchar_t&);
bool myIsUpper(wchar_t&);
bool myCyr(wchar_t&);

int main() {
	locale::global(locale("ru_RU.UTF-8"));
	wstring myStr;
	wcout << L"Введите строку:\n";
	getline(wcin, myStr);
	for (auto &r : myStr)
		if (!myIsUpper(r) && myCyr(r))
			myToUpper(r);
	wcout << myStr << endl;
	return 0;
}
// меняет регистр символов
void myToUpper(wchar_t &ch) {
	ch = wchar_t(int(ch) - 32);
}
// проверка является ли символ в верхнем регистре
// true, если да
bool myIsUpper(wchar_t &ch) {
	return int(ch) > 1039 && int(ch) < 1072;
}
// проверка, что это кириллический символ Unicode
// true, если да
bool myCyr(wchar_t &ch) {
	return int(ch) > 1023 && int(ch) < 1280;
}

Числовые преобразования

Для преобразования строковых значений в числа (и наоборот) существует набор функций преобразования. Преобразования объекта класса string осуществляются в знаковое и беззнаковое целое и в число с плавающей точкой.

stoi, stol, stoll

Эти функции преобразуют строку в целое число со знаком. Функции имеет три аргумента:

stoi(myStr, pos, base)
  • myStr – строка для преобразования
  • pos – адрес переменной целочисленного типа для сохранения в ней индекса первого непреобразованного символа. Необязательный аргумент
  • base – основание системы счисления. Необязательный аргумент

Например:

string S1("45");
string S2("65");
int d = stoi(S1) + stoi(S2);
cout << d << endl;
stoul, stoull

Эти функции осуществляют аналогичные преобразования, но в беззнаковое целое.

stof, stod, stold

Эти функции преобразуют строку в число с плавающей точкой. Функции имеют два аргумента:

stof(myStr, pos)

Второй аргумент необязательный.

to_string, to_wstring

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


Print Friendly, PDF & Email

Comments are closed.