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

Методы-модификаторы строки. Конкатенация. Метод append()

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

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

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

operator+=(str); // str - объект string, string_view или указатель на C-строку
operator+=(char);
operator+=(ilist);
append

Операция "+=" более лаконична, но метод append() гибче, так как позволяет добавлять элементы гораздо большими способами:

myStr.append(str); // str - объект string, string_view или указатель на C-строку
myStr.append(other_str, pos, count); //объект string или string_view
myStr.append(С-str, count); // C-str - указатель на C-строку
myStr.append(firs, last);
myStr.append(count, char); 
myStr.append(ilist); // список инициализации

2. Добавляет подстроку из строки other_str в промежутке [pos, pos + count);
3. Добавляет count символьного C-массива на который указавает указатель C-str;
4. Добавляет подстроку определённую итераторами first и last ([first, last));
5. Добавляет count символов char;
Решим следующую задачу. Дана непустая строка S и целое число N (> 0). Вывести строку, содержащую символы строки S, между которыми вставлено по N символов '*'.

Программа cpp-27.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() - 1) + 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 << endl;
    return 0;
}

Вывод

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

Методы replace(), resize(), clear() и assign()

replace

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

myStr.replace(pos, count, other_str);
myStr.replace(pos, count, other_str, pos2, count2);
myStr.replace(first, last, other_str);
myStr.replace(first, last, first2, last2);
myStr.replace(first, last, C-str, count);
myStr.replace(first, last, count, char);
myStr.replace(pos, count, count2, char);
myStr.replace(first, last, ilist);

1, 2. Замена подстроки, указанную диапазоном [pos, pos + count), на строку other_str (или диапазоном).
1, 3. other_str может быть объектом класса string, C-строкой или string_view.
2. other_str может быть объектом класса string или string_view.
3, 4, 5, 6, 8. Заменяет в текущей строке подстроку, взятую итераторами [first, last), на other_str, другим диапазоном [first2, last2), count символами ([С-str, С-str + count)) массива C-str, count символами char или списком инициализации ilist.
7. Замена подстроки, указанную диапазоном [pos, pos + count) на count2 символов char.

Программа cpp-27.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, то будут добавлены дополнительные символы char (не обязательный аргумент).

myStr.resize(count, ch)
clear

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

assign

Этот метод полностью заменяет содержимое строки.

myStr.assign(count, char);
myStr.assign(other_str);
myStr.assign(other_str, pos, count);
myStr.assign(C-str, count);
myStr.assign(first, last);
myStr.assign(ilist);

2, 3. Замена строкой other_str (other_str может быть объектом класса string, указателем на C-строку или string_view) или диапазоном.
2. Если other_str объект класса string, то строка может быть либо скопирована, либо перемещена (эквивалентно: *this = std::move(other_str)).
Рассмотрим пример. Дана строка S1. Эта строка делится пополам. Первая половина строки помещается в строку S2, а вторая - в строку S3. Заменить исходную строку на строку, состоящую из 20 символов '*'. Вывести строки S1, S2 и S3. Изменить строки S1 и S2 так, чтобы первые семь символов строки S2 и последние семь символов строки S3 были заменены на символы строки S1. Вывести модифицированные строки S2 и S3.

Программа cpp-27.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********************
Примечание. Обратите внимание на конкатенацию строковых литералов. Если строка слишком длинная ее можно разделить, при этом, каждая часть должна заключаться в отдельные двойные кавычки. Символы конца строки, вне двойных кавычек, будут проигнорированы.

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

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

erase

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

myStr.erase(index, size_type count);
myStr.erase(const_iterator position);
myStr.erase(const_iterator first, const_iterator last);

Первый вариант удаляет min(count, size() - index) символов, начиная с позиции index. Возвращается указатель на объект. Следующие две функции возвращают итератор указывающий на символ, следующий за последним удаленным символом. Второй вариант удаляет символ в позиции итератора, а третий - в диапазоне [first, last).
Зададимся вопросом: "Что будет работать быстрее, копирование нужных символов в другую (пустую) строку или удаление ненужных символов в текущей?". Вопрос не праздный, но ответ на него совсем не очевидный. Это пример того, что разработчикам необходимо проверять различные варианты решения той или иной задачи. Итак, дана строка содержащая числовые символы и любые другие символы. Необходимо получить новую строку содержащую только числовые символы. Воспользуемся библиотекой chrono для определения времени выполнения фрагментов программы.

Программа cpp-27.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() работает быстрее, нежели с копированием и операцией "+=".
Стандарт C++20 ввел еще две функции (void) не являющиеся методами класса (определены в заголовке string):

erase(str, value);
erase_if(str, pred);

Первая удаляет все символы value в строке str, вторая - удаляет символы, для которых унарный предикат pred возвращает значение true.
Обе функции возвращают количество удаленных символов.

insert

Этот метод имеет довольно много перегрузок. Перегрузки возвращающие ссылку на объект:

myStr.insert(index, count, ch);
myStr.insert(index, str); // str - объект string или указатель на С-строку
myStr.insert(index, str, count); // str - указатель на С-строку
myStr.insert(index, str, index_str, count); // str - объект string

1. Вставка count символов ch в позицию index;
2. Вставка строки str в позицию index;
3. Вставка диапазона [str, str+count) в позицию index;
4. Вставка подстроки str.substr(index_str, count) в позицию index;
Перегрузки ниже возвращают итератор вставки (в позиции следующей за последним вставленным символом). Вставляют символ, определенное количество символов, диапазон или список инициализации в позицию итератора it, соответственно:

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

find() находит первую подстроку, равную переданной строке, подстроке или символу (char). В качестве подстроки может выступать объект класса string, string_view или C-строка:

myStr.find(str, pos);
myStr.find(C-str, pos, count);
myStr.find(C-str, pos);
myStr.find(char, pos);

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

rfind

Для поиска последней подстроки равной переданной строке, подстроке или символу (char) используется аналогичный метод rfind().

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

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

Программа cpp-27.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-27.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-27.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-27.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) для извлечения и удаления его из входного строкового потока

Примеры решения задач
  • Посимвольный анализ строки, изменение символов в строке. Дана строка латинских символов (четверостишие), с ограничителем строки – точка (в конце строки). Определить в этом четверостишии количество слов и вывести эту строку, преобразовав символы этой строки к верхнему регистру.
  • #include <iostream>
    #include <string>
    using namespace std;
    
    int main() {
    	string myStr;
    	int k = 0;
    	cout << "Введите строку:" << endl;
    	getline(cin, myStr, '.');
    	for (auto i = 0; i < myStr.size(); i++) {
    		if (myStr[i] == ' ' || myStr[i] == '\n') k++;
    		myStr[i] = toupper(myStr[i]);
    	}
    	cout << "\nВ этом предложении " << k + 1 << " слов" << endl;
    	cout << "Измененная строка:" << endl << endl;
    	cout << myStr << '.' << endl;
    	return 0;
    }
    
    Введите строку:
    My lonely heart athirst, I trod
    A barren waste when, so 'twas fated,
    A winged seraph 'fore me stood:
    Where crossed the desert roads he waited.
    
    В этом предложении 26 слов
    Измененная строка:
    
    MY LONELY HEART ATHIRST, I TROD
    A BARREN WASTE WHEN, SO 'TWAS FATED,
    A WINGED SERAPH 'FORE ME STOOD:
    WHERE CROSSED THE DESERT ROADS HE WAITED.
    

    Примечание. Инструкция цикла for основанная на диапазоне может сделать вашу программу значительно лаконичнее (в переменную s будут передаваться символы строки):

     for (auto &s : myStr) {
            if (s == ' ' || s == '\n') k++;
            s = toupper(s);
        }
    
  • Изменение строки. Удаление символов. Дана строка S. Слова в этой строке разделены одним или более пробелами. Пробелы могут быть в начале и в конце строки. Удалить лишние пробелы (а также пробелы в начале и в конце), оставив между словами по одному пробелу.
  • #include <iostream>
    #include <string>
    using namespace std;
    
    int main() {
        string S;
        size_t i = 0;
        cout << "Введите строку:" << endl;
        getline(cin, S);
        // удаляем пробелы в начале строки
        while (S[i] == ' ') S.erase(i, 1);
        // теперь будем удалять с конца
        i = S.size();
        while (i > 0) {
        	--i;
        	// удаляем все пробелы в конце
            if (i == S.size() - 1 && S[i] == ' ')
            	while (S[i] == ' ')
            		S.erase(i, 1);
            // пропускаем один для разделения слов
            if (S[i] == ' ')
            	--i;
            // остальные удаляем
            while (S[i] == ' ')
            	S.erase(i, 1);
        }
        cout << "Модифицированная строка: " << endl;
        cout << S << endl;
        return 0;
    }
    
    Введите строку:
       ww  wwwww    wwww
    Модифицированная строка: 
    ww wwwww wwww
    
  • Изменение строки. Поиск и замена символов. Даны строки S, S1 и S2. Заменить в строке S все вхождения строки S1 на строку S2.
  • #include <iostream>
    #include <string>
    using namespace std;
    
    int main() {
        string S, S1, S2, Scopy;
        cout << "Введите строку S:" << endl;
        getline(cin, S);
        // S изменится, поэтому сохраняем
        Scopy = S;
        cout << "Введите подстроку S1:" << endl;
        getline(cin, S1);
        cout << "Введите строку замены S2:" << endl;
        getline(cin, S2);
        size_t k = S1.size();
        while (S.find(S1) != string::npos) {
            // замена k символов подстроки S1,
        	// начиная с позиции i,
            // на символы подстроки-замены - S2
        	size_t i = S.find(S1);
            S.replace(i, k, S2);
        }
        cout << "Исходная строка:\n"
             << Scopy << endl
             << "Модифицированная строка:\n"
             << S << endl;
    
        return 0;
    }
    
    Введите строку S:
    qqqqqwwqqqqqqww
    Введите подстроку S1:
    ww
    Введите строку замены S2:
    ssss
    Исходная строка: 
    qqqqqwwqqqqqqww
    Модифицированная строка: 
    qqqqqssssqqqqqqssss
    
Вопросы
Темы сообщений
Задания А
Задания Б
Задания С
Ссылки
1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 4,00 из 5)
Загрузка...

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Print Friendly, PDF & Email

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