§18 Операции с объектом класса string. Строковые потоки

Конкатенация. Метод append()

В операции конкатенации (лат. concatenatio – «сцепле́ние») происходит объединение строк. Результатом сложения двух строк – является новая строка string. В этих операциях могут участвовать наряду с объектами класса string строковые литералы, одиночные символы и C-строки. Если объекту класса string осуществляется попытка присвоить значение конкатенации литералов (в конкатенации не участвуют объекты класса string), то это приведет к ошибке. Для конкатенации применяются операции "+" и "+=". Перегруженная операция "+=" трактуется как “добавление в конец” строки. Например:

string S1 = "Это";
string S2 = "новая";
string S3 = "строка";
string new_S = S1 + " " + S2 + " " + S3;
new_S += '!';
cout << new_S << endl;
append

Фактически операция "+=" может, в некоторых случаях, заменить существующий метод append(). Однако данный метод гибче, так как позволяет добавлять элементы различными способами, например:
1. подстроку из строки other_str в промежутке [pos, pos + count):

myStr.append(other_str, pos, count);

2. подстроку определённую итераторами first и last:

myStr.append(firs, last);

3. или более лаконичное добавление count символов ch:

myStr.append(count, ch);

Решим следующую задачу. Дана непустая строка S и целое число N (> 0). Вывести строку, содержащую символы строки S, между которыми вставлено по N символов '*'.

Программа cpp-26.1
#include <iostream>
#include <string>
using namespace std;

int main() {
	string S;
	size_t i(0), N;
	cout << "Введите строку:\n";
	getline(cin, S);
	cout << "Введите число N:\n";
	cin >> N;
	string subS(N, '*');
	string *temp = new string; // создаем временную строку
	temp->reserve(N * S.size());
	while (i < S.size()) {
		*temp += S[i] + subS;
		i++;
	}
	S.assign(*temp); // заменяем S на temp
	delete temp; // удаляем временную строку
	S.resize(S.size() - subS.size());
	cout << "Результат:\n"
		 << S;
	return 0;
}

Вывод

Введите строку:
QQQQQQQQQQQ
Введите число N:
3
Результат:
Q***Q***Q***Q***Q***Q***Q***Q***Q***Q***Q

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

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

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)
Программа cpp-26.2

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

#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.

Программа cpp-26.3
#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.");
	string S2, S3;
	auto first = S1.сbegin();
	auto last = S1.сend();
	// Используем итераторы
	S2.assign(first, last - S1.size() / 2);
	S3.assign(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). Оба варианта возвращают итератор, указывающий на символ, следующий за последним удаленным символом.
Зададимся вопросом: "Что будет работать быстрее, копирование нужных символов в другую (пустую) строку или удаление ненужных символов в текущей?". Вопрос не праздный, но ответ на который не всегда очевидный. Разработчикам необходимо проверять различные варианты решения той или иной задачи. Итак, дана строка содержащая числовые символы и любые другие символы. Необходимо получить новую строку содержащую только числовые символы. Воспользуемся библиотекой chrono для определения времени выполнения фрагментов программы.

Программа cpp-26.4
#include <iostream>
#include <string>
#include <chrono>

using namespace std;
using namespace chrono;

int main() {
    string S1;
    cout << "S1 => "; getline(cin, S1);
    string *S2 = new string; // создаем временный объект
    auto start = system_clock::now();
    for (auto r : S1) {
        if (isdigit(r))
            *S2 += r;
    }
    cout << "S2 => " << *S2 << endl;
    auto end = system_clock::now();
    auto elapsed = end - start;
    cout << duration_cast<microseconds>(elapsed).count()
         << " мкс"
         << endl;
    delete S2; // удаляем временный объект
    start = system_clock::now();
    auto first = S1.begin();
    while (first != S1.end()) {
        if (!isdigit(*first))
            first = S1.erase(first);
        else
            first++;
    }
    cout << "S1 => " << S1 << endl;
    end = system_clock::now();
    elapsed = end - start;
    cout << duration_cast<microseconds>(elapsed).count()
         << " мкс"
         << endl;
    return 0;
}

Вывод

S1 => zsdrfgh43zd54f63z4df4gh3zsdujh45xfk3mx3fhdkl43yi5jxu36dt45zyh3dx5^X@s7^X
@sd^X@sr^X@s4jsdj534zdrfuyi[45gu354p;gui35p4[3gho4iok3d*jhtayh3e4r3qer4wt3er
S2 => 4354634434533435364535745344535435434334343
24 мкс
S1 => 4354634434533435364535745344535435434334343
13 мкс

Тесты показывают, что фрагмент с использованием метода erase() работает быстрее, нежели с копированием и операцией "+=".

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-строкой):

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

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

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

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

Программа cpp-26.5
#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. Вывести измененные строки.

Программа cpp-26.6
#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;
}

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

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

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

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

Программа cpp-26.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). Если функция имеет аргумент (которым является передаваемая строка), то аргумент будет копироваться в потоковый объект.
Рассмотрим пример задачи, в которой применяется как входной, так и выходной строковый поток.
Дана строка, в которой слова разделены пробелами (пробелы не повторяются). Получить новую строку в которой слова разделены символом '.' (точка). В конце строки точки не должно быть.

Программа cpp-26.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 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (Пока оценок нет)
Загрузка...
Print Friendly, PDF & Email

Обсуждение закрыто.