§20 Методы класса string. Посимвольная обработка. Числовые преобразования. Строковые потоки

Методы-модификаторы строки

Класс string содержит большое количество функций-членов (методов) для изменения строки. Этих функций гораздо больше, чем в других контейнерах. Объясняется это тем, что данные методы обеспечивают тот необходимый функционал, который соответствует функционалу для работы с C-строкой. Эти методы, в совокупности, называются модификаторами, так как они изменяют символьный массив различным образом. С точки зрения эффективности, эти методы могут влиять на производительность программы негативно, так как в результате может произойти прераспределение памяти (см. предыдущий урок). Нужно тщательно взвесить, стоит ли использовать в своей программе тот или иной метод. Например, в задаче 19.8 мы использовали временную строку для получения результата. Однако, если использовать, для решения задачи метод insert(), применяемый к исходной строке, то вы бы получили чрезвычайно медленно работающую программу (на каждом шаге цикла происходило бы смещение элементов с копированием новых, а это не эффективно). Рассмотреть всё разнообразие работы с этими методами, в рамках нашего курса, не представляется возможном.

replace

Этот модификатор заменяет в исходной строке подстроку строкой или указанным диапазоном. Подстрока замены не обязательно должна иметь тот же размер, что и заменяемая подстрока.
1. Замена подстроки, указанную диапазоном [pos, pos + count), на строку other_str (other_str может быть как объектом класса string, так и C-строкой):

myStr.replace(pos, count, other_str)

или с указанием диапазона из other_str

myStr.replace(pos, count, other_str, pos2, count2)

2. Подстроку для замены можно получить и с помощью итераторов:

myStr.replace(first1, last1, first2, last2)

3. Наконец, можно заменить count символами ch:

myStr.replace(first, last, count, ch)

Программа 20.1 В заданной строке поменять местами первое и последнее слово строки. Разделителями слов считаются пробелы.

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

int main() {
    string S;
    cout << "Введите строку:";
    getline(cin, S);
    size_t s = S.find(' ');
    size_t e = S.rfind(' ');
    string subS1 = S.substr(0, s);
    string subS2 = S.substr(e + 1, string::npos);
    S.replace(0, subS1.size(), subS2);
    e = S.rfind(' ');
    S.replace(e + 1, subS2.size(), subS1);
    cout << S << endl;
    return 0;
}
resize

resize() изменяет размер строки, чтобы она могла содержать count символов. Если текущий размер меньше, чем count, то будут добавлены дополнительные символы ch (не обязательный аргумент).

myStr.resize(count, ch)

clear

Метод clear() удаляет все символы строки. Но объем массива (capacity) остается неизменным.

assign

Этот метод заменяет содержимое строки
1. Либо строкой other_str (other_str может быть как объектом класса string, так и C-строкой):

myStr.assign(other_str)

2. либо диапазоном на который указывают итераторы:

myStr.assign(first, last)

3. либо (что аналогично предыдущему) подстрокой [pos, pos + count) из строки other_str. В этом случае pos не должен превышать myStr.size().

myStr.assign(other_str, pos, count)

Рассмотрим пример. Дана строка S1. Эта строка делится пополам. Первая половина строки помещается в строку S2, а вторая – в строку S3. Заменить исходную строку на строку, состоящую из 20 символов '*'. Вывести строки S1, S2 и S3. Изменить строки S1 и S2 так, чтобы первые семь символов строки S2 и последние семь символов строки S3 были заменены на символы строки S1. Вывести модифицированные строки S2 и S3.
Программа 20.2

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

int main() {
	string S1("The C++ programming language has support for string handling, "
			"mostly implemented in its standard library."
			);
    auto first = S1.cbegin();
    auto last = S1.cend();
	// Используем итераторы
	string S2(first, last - S1.size() / 2);
	string S3(first + S1.size() / 2, last);
	cout << "S1 => " << S1 << endl;
	S1.assign(20, '*');
	cout << "S2 => " << S2 << "\n"
		 << "S3 => " << S3 << "\n"
		 << "S1 => " << S1 << endl;
	// Используем диапазоны
	S2.replace(0, 7, S1);
	S3.replace(S3.size() - 7, 7, S1);
	cout << "S2 => " << S2 << "\n"
		 << "S3 => " << S3 << endl;
	return 0;
}
S1 => The C++ programming language has support for string handling, mostly implemented in its standard library.
S2 => The C++ programming language has support for string h
S3 => handling, mostly implemented in its standard library.
S1 => ********************
S2 => ******************** programming language has support for string h
S3 => handling, mostly implemented in its standard l********************

Методы empty, erase, insert, find и константа npos

Не смотря на то, что erase() и insert() формально являются модификаторами, они часто используются в программе с методом find(), поэтому мы рассмотрим работу с этими методами в этой части занятия.

empty

Метод empty() проверяет строку на отсутствие в ней символов. Это логическая функция, она возвращает true, если строка пуста, иначе false().

erase

Этот метод удаляет указанные символы из строки. erase() может использоваться в двух вариантах:

myStr.erase(it);
myStr.erase(first, last);

Первый вариант удаляет символ в позиции итератора, второй – в диапазоне [first, last). Оба варианта возвращают итератор, указывающий на символ, следующий за последним удаленным символом.

insert

Вставляет символ, определенное количество символов или диапазон символов в позицию итератора it:

myStr.insert(it, ch);
myStr.insert(it, count, ch);
myStr.insert(it, first, last);

Во всех случаях возвращается итератор вставки (в позиции следующей за последним вставленным символом).

find

find() находит первую подстроку, равную переданной строке, подстроке или символу (ch), начиная с позиции pos, необязательный аргумент count – первые count символов строки other_str (other_str может быть как объектом класса string, так и C-строкой). Если pos == 0, то опустить можно и его. Возвращает size_type.

myStr.find(ch, pos);
myStr.find(other_str, pos);
myStr.find(other_str, pos, count);

Метод возвращает позицию первого символа найденной подстроки или npos (см. ниже), если подстрока не найдена.
Для поиска подстроки не с начала, а с конца, существует аналогичный метод rfind().

Константа string::npos

Это специальное значение, равное максимальному значению, которое может предоставить тип size_type. Точный смысл данного значения зависит от контекста, но, как правило, оно используется либо как индикатор конца строки в функциях, которые ожидают позицию символа (программа 20.1), либо как индикатор ошибки в функциях, которые возвращают позицию в строке (программа 20.3).
Пример задачи. Даны строки S и S0. Удалить из строки S все подстроки, совпадающие с S0. Если совпадающих подстрок нет, то вывести строку S без изменений.
Программа 20.3

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

int main() {
	string S, S0;
	cout << "Введите строку" << endl;
	getline(cin, S);
	cout << "Введите подстроку" << endl;
	getline(cin, S0);
	if (S.find(S0) == string::npos)
		cout << S << endl;
	else {
		while (S.find(S0) != string::npos){
			auto pos = S.find(S0);
			S.erase(pos, S0.size());
		}
		cout << S << endl;
	}
	return 0;
}
Введите строку
wwwqqwwwqqqwwwqqqqwww
Введите подстроку
qq
wwwwwwqwwwwww

Методы copy, substr и swap

Эти три метода предназначены для копирования.

copy

Метод copy() копирует подстроку, заданную диапазоном [pos, pos + count), в строку символов, на которую указывает other_str. pos не должен быть >= size().

myStr.copy(other_str, count, pos)
substr

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

other_str = myStr.substr(pos, count)
swap

Метод swap() позволяет обмениваться содержимым между двумя строками myStr и other_str.

myStr.swap(othe_str)

Рассмотрим пример задачи. Даны целые положительные числа N1 и N2 и строки S1 и S2. Получить из этих строк новую строку, содержащую первые N1 символов строки S1 и последние N2 символов строки S2 (в указанном порядке). Произвести обмен элементами между строками S1 и S2. Вывести измененные строки.
Программа 20.4

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

int main() {
	string S1, S2, S3;
	size_t N1, N2;
	cout << "S1 => ";
	getline(cin, S1);
	cout << "S2 => ";
	getline(cin, S2);
	cout << "N1 = "; cin >> N1;
	cout << "N2 = "; cin >> N2;
	S3 = S1.substr(0, N1) +
		 S2.substr(S2.size() - N2, string::npos);
	S1.swap(S2);
	cout << "S3 => "
		 << S3 << "\n"
		 << "S1 => "
		 << S1 << "\n"
		 << "S2 => "
		 << S2 << endl;
	return 0;
}

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

Для доступа к отдельному символу строки применяются либо операция обращения по индексу [] (например, myStr[i], как в C-массивах), либо метод at() (например, myStr.at(i)). При этом используется обычный цикл for. Если не предвидится изменение размера строки, то лучшим выбором будет не обычный for, а range-based for. Метод 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) нельзя использовать для массивов, которые, в процессе обработки, изменяют свой размер (а таковым может являться объект класса string).
Если используется заголовочный файл 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)!
Программа 20.5

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

int main() {
	locale::global(locale("ru_RU.UTF-8"));
	int i = 0, j = 0;
	wstring myStr;
	wcout << L"Введите строку:\n";
	getline(wcin, myStr);
	for (auto &r : myStr) {
		if (iswalpha(r)) i++;
		if (iswspace(r)) j++;
		if (iswlower(r))
			r = towupper(r);
		else
			r = towlower(r);
	}
	wcout << L"В строке:\n"
		  << i << L" букв\n"
		  << j << L" пробелов\n"
		  "Новая строка:\n"
		  << myStr
		  << endl;
	return 0;
}
Введите строку:
Введите строку:
в этой строке есть русские АБВГДЕЁЖ и английские ABCDEFGHIJ буквы
В строке:
56 букв
9 пробелов
Новая строка:
В ЭТОЙ СТРОКЕ ЕСТЬ РУССКИЕ абвгдеёж И АНГЛИЙСКИЕ abcdefghij БУКВЫ

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

Функции числовых преобразований (вошли в стандарт C++11) применяются для строк типов string и wstring (никаких дополнительных заголовков не требуется).

stoi(), stol(), stoll()
  • преобразует строку в знаковое целое число
  • stoi(str, pos, base)
    stol(str, pos, base)
    stoll(str, pos, base)
    
    stoul(), stoull()
  • преобразует строку в беззнаковое целое число
  • stoul(str, pos, base)
    stoull(str, pos, base)
    

    str – строка для преобразования, pos – адрес переменной целочисленного типа для сохранения в ней индекса первого непреобразованного символа, base – основание системы счисления. Для оснований больше, чем 10, набор корректных цифр дополняется символами латинского алфавита, начиная с Aa для системы счисления с основанием 11, до Zz для системы счисления с основанием 36. Регистр символов значения не имеет.

    stof(), stod(), stold()
  • преобразует строку в число с плавающей точкой
  • stof(str, pos)
    stod(str, pos)
    stold(str, pos)
    

    str – строка для преобразования, pos – адрес переменной целочисленного типа для сохранения в ней индекса первого непреобразованного символа.

    to_string(), to_wstring()
  • преобразует целое число или число с плавающей точкой в string
  • to_string(value)
    
  • преобразует целое число или число с плавающей точкой в wstring
  • to_wstring(value)
    

    value – числовое значение для преобразования

Программа 20.6

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

int main() {
	string S1 = "10000";
	string S2 = "10.2";
	cout << stoi(S1) * 2 << endl;
	cout << stod(S2) / 5 << endl;
	return 0;
}
20000
2.04

Строковые потоки

Ранее мы обсудили возможность использования цикла while и потока ввода для чтения введенной строки по словам. Однако лучшей альтернативой такому подходу будет использование класса строковых потоков. Строковые потоки осуществляют ввод/вывод в оперативной памяти и используют буфер для записи в строку и чтения из строки, как стандартные или файловые потоки. Заголовок sstream определяет три класса строковых потоков:

  • istringstream Строковый ввод
  • ostringstream Строковый вывод
  • stringstream  Строковый ввод и вывод

Эти классы наследуют методы классов istream, ostream и iostream, соответственно. Если для стандартных потоков объекты ввода/вывода (cin и cout) предопределены, то для строковых потоков они должны быть созданы разработчиком. (Разумеется, необходимо соблюдать правила использования идентификаторов. Например, допустимыми именами объектов могут быть: is, os, stream, record и т. п.).
Объект строкового ввода создается как экземпляр класса istringstream.
Аналогично, объект потока строкового вывода создается как экземпляр класса ostringstream.
Строковые потоки очень удобно применять, когда требуется производить анализ и изменение слов в строке. Таким образом, можно осуществлять контроль пользовательского ввода/вывода. Рассмотрим применение входного строкового потока.
Вводится пять строк. Определить в тексте количество букв в самом длинном слове.
Программа 20.7

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

int main() {
	string line, word;
	unsigned int max = 0, k = 5;
	cout << "Введите 5 строк: " << endl;
	while (k--) {
		getline(cin, line);
		// Создаем из строки строковый поток
		istringstream stream(line);
		// Работаем с каждым словом по отдельности
		while (stream) {
			stream >> word;
			if (word.size() > max) max = word.size();
		}
	}
	cout << "Самое большое слово состоит из " << max << " букв" << endl;
	return 0;
}
Введите 5 строк: 
qqq qqqqqq qqqqqqqqqqqqqqq
q q q
qqq qqq qqq
q qqq q qqq
qqqqqqqqq qqqqqqqqq
Самое большое слово состоит из 15 букв
Метод str()

Для строковых потоков описан специальный метод str(). Этот метод недоступен в других потоковых классах. Он выполняет двоякую роль. Если функция не имеет аргументов (как в программе, ниже), то возвращается копия строки, которую хранит потоковый объект (os). Если функция имеет аргумент (которым является передаваемая строка), то аргумент будет копироваться в потоковый объект.
Рассмотрим пример задачи, в которой применяется как входной, так и выходной строковый поток.
Дана строка, в которой слова разделены пробелами (пробелы не повторяются). Получить новую строку в которой слова разделены символом '.' (точка). В конце строки точки не должно быть.

Программа 20.8

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

int main() {
	string line, word;
	cout << "Введите строку: " << endl;
	getline(cin, line);
	istringstream is(line);
	ostringstream os;
	while (is) {
		is >> word;
		is.ignore();
		os << (!is ? word : word + ".");
	}
	cout << os.str() << endl;
	return 0;
}
Введите строку: 
q qq qqq qqqq qqqqq qqqqqq
q.qq.qqq.qqqq.qqqqq.qqqqqq

Для того, чтобы завершающий (нулевой) символ не воспринимался как еще одно слово, мы воспользовались методом ignore() (наследуется из базового класса istream) для извлечения и удаления его из входного строкового потока

Домашнее задание
Презентация
Литература
  1. Прата, Стивен. Язык программирования C++. Лекции и упражнения, 6-е изд.: Пер. с англ. — М.: ООО «И.Д. Вильяме», 2012
  2. Липпман Б. Стенли, Жози Лажойе, Барбара Э. Му. Язык программирования С++. Базовый курс. Изд. 5-е. М: ООО "И. Д. Вильямс", 2014
  3. Эллайн А. C++. От ламера до программера. СПб.: Питер, 2015
  4. Джосаттис Н. М. Стандартная библиотека C++: справочное руководство, 2-е изд.-М.: Вильямс, 2014
  5. Широкий символ


Print Friendly, PDF & Email

Comments are closed