Глава 3
Вверх
Каждому программисту с опытом работы на С хорошо знакомы функции записи, поиска, модификации и копирования массивов char. У функций стандартной библиотеки С имеются два недостатка.
Во первых они неформально делятся на две категории: "простые" функции и те, которым при вызове необходимо передать количество символов, участвующих в выполняемой операции. Список библиотечных функций С для работы с символьными массивами поражает неопытного пользователя: перед ним оказывается длинный перечень загадочных и неудобопроизносимых имен функций. Типы и количество аргументов у этих функций более или менее выдержаны, но для их правильного использования необходимо тщательно следить за именами и передачей параметров.
Однако у функций стандартной библиотеки С есть второй недостаток: все они подразумевают, что символьный массив завершается нуль-терминатором. Если нуль-символ по недосмотру или ошибке будет пропущен или перезаписан, ничто не помешает функциям символьных массивов С выйти за пределы строки. Результаты обычно оказываются катастрофическими.
Строковые объекты С++ существенно превосходят свои прототипы С по удобству и безопасности. Количество имен функций класса string примерно соответствует количеству функций в библиотеке С, но механизм перегрузки существенно расширяет их функциональность. В сочетании с разумными правилами выбора имен и выбором аргументов по умолчанию, работать с классом string гораздо проще и удобнее, чем с функциями символьных массивов стандартной библиотеки С.
Вверх
Одно из самых ценных и удобных свойств строк С++ состоит в том, что они автоматически растут по мере надобности, не требуя вмешательства со стороны программиста. Работа со строками не только становится более надежной, из нее почти полностью устраняются "нетворческие" операции - отслеживание границ памяти, в которой хранятся данные строки.
Например, если при создании строковый объект был инициализирован 50 экземплярами символа "X", а позднее в нем были сохранены 50 экзнемпляров строки "Zowie", объект сам выделит достаточный блок памяти в соответствии с увеличившимся объемом данных.
Но в полной мере это свойство проявляется в ситуациях, когда обрабатываемые в программе строки изменяются в размерах, но вы не можете оценить эти изменения количественно.
Строковые функции append() и insert() автоматически перераспределяют память при увеличении строки:
int main() { // Вставка строки в bigNews в позицию перед bigNews[1]
// Резервирование достаточного объема памяти
I
saw Elvis in a UFO.
Анализ:
Из приведенного примера видно, что хотя вы снимаете с себя большую часть ответственности за выделение памяти и управление ею в строках, класс string предоставляет в ваше распоряжение ряд средств для контроля за их размером. Обратите внимание как легко был изменен размер блока памяти, выделенного под хранение символов строки.
Функция size() возвращает текущее количество символов, она идентична функции length().
Вверх
Если вы хотите, чтобы существующие символы были заменены новыми, воспользуйтесь функцией перезаписи replace(). Существует несколько перегруженных версий replace(), но простейшая форма получает три аргумента:
int main() {
A piece hello there of text
Анализ:
Строка tag сначала вставляется в s (обратите внимание: вставка производится перед
заданной позицией, и после tag еще вставляется дополнительный пробел). Далее выполняются операции поиска и замены.
АВ: В данной программе в третий аргумент функции replace() передается строка, большая по размеру, чем та, которую надо заменить. Тем не менее убирается при замене пять символов, а на их место вставляется строка из 8(!) символов. То есть три символа заменили на 8 символов, часть строки, оставшаяся после вставки просто подвинулась!
Прежде, чем вызвать replace(), стоит проверить удалось ли найти искомую подстроку. В предыдущем примере для замены использовался тип char*, но существует перегруженная версия replace() с аргументом типа string. Далее приводится более полный пример с заменой подстроки:
Вверх
Функция замены одной подстроки - другой подстрокой:
void
replaceChars
(string& modifyMe,
const string& findMe, const string& newChars) {
int main() {
I thought I saw Elvis in a wig. I have been working too hard.
Анализ:
АВ: То, что мы делали в предыдущей программе, в этой программе оформлено в виде функции:
void
replaceChars
(string& modifyMe,
const string& findMe, const string& newChars) {
Если функция replace() не находит искомую подстроку, она возвращает string::npos - статическую константу класса string, которая представляет несуществующую позицию символа.
(Наибольшее значение, которое по умолчанию может быть представлено для типа size_type строкового распределителя памяти (std::size_t по умолчанию))
В отличие от insert(), функция replace() не расширяет область данных строки при копировании в середину существующей последовательности символов. Тем не менее, она расширяет область данных при необходимости, например, если в результате замены исходная строка выходит за пределы текущего блока.
АВ: то есть функция replace() заменяет одну последовательность символов другой последовательностью символов начиная с определенной позиции. И только если новая строка превосходит по размеру исходную, то увеличивает размер строки и вставляет все новые символы. А функция insert() деликатно сдвигает символы исходной строки, а на освободившееся место ставит новые символы. Размер новой строки естественно сразу увеличивается по заданному компилятором алгоритму.
Пример:
Вверх
int main() {
I have been working the graveyard shift.
Анализ:
Вызов replace() начинает замену за концом существующего массива, что эквивалентно операции присоединения символов. В этом случае функция replace() соответствующим образом расширяет массив.
Возможно вы наскоро просматриваете эту главу в поисках рецепта для выполнения простой операции, такой как замена всех вхождений одного символа другим символом.
Такую функцию легко написать на базе функций find() и replace().
Вверх
Функция замены всех вхождений одной подстроки - другой подстрокой:
std::string& replaceAll(string& context,
string& replaceAll(string& context, const string& from,
const string& to) {
Версия find(), использованная в этой программе, получает во втором аргументе начальную позицию поиска, и возвращает string::npos. Позицию, хранящуюся в переменной lookHere, важно сместить за строку замены на тот случай, если from является подстрокой to.
Следующая программа предназначена для тестирования функции replaceAll():
int main() {
a mXXX, a plXXX, a cXXXal, pXXXama
Анализ:
Как видите класс string сам по себе не решает все возможные задачи - во многих решениях приходится привлекать алгоритмы стандартной библиотеки (подробнее в гл 6), поскольку класс string может рассматриваться как контейнер STL ( для чего используются итераторы, о которых говорилось выше).
Все общие алгоритмы работают с "интервалами" элементов, хранящихся в контейнерах. Чаще всего используются интервалы "от начала до конца" контейнера. Объект string интерпретируется как контейнер с элементами-символами; начало интервала определяется итератором string::begin(), а конец интервала - итератором string::end().
Вверх
Следующий пример демонстрирует применение алгоритма replace()
int main() {
Анализ:
Обратите внимание алгоритм replace() вызывается не как функция класса string.
Кроме того, в отличие от функций string::replace(), выполняющих только одну замену,
Алгоритм replace() работает только с отдельными объектами ( в данном случае с объектами char), он не может использоваться для замены символьных массивов или объектов string. Поскольку объект string ведет себя как контейнер STL, к нему могут применяться и другие алгоритмы. Это позволяет решать задачи, не решаемые напрямую функциями класса string.
Назад |
Начало урока |
Вверх |
Вперед
Операции со строками
Присоединение, вставка и конкатенация строк
append(),insert()
//: C03:StrSize.cpp
#include <string>
#include <iostream>
using namespace std;
cout << bigNews << endl;
// Сколько данных фактически получено?
cout << "Size = " << bigNews.size() << endl;
// Сколько данных можно сохранить без перераспределения памяти?
cout << "Capacity = "
<< bigNews.capacity() << endl;
bigNews.insert(1, " thought I");
cout << bigNews << endl;
cout << "Size = " << bigNews.size() << endl;
cout << "Capacity = "
<< bigNews.capacity() << endl;
bigNews.reserve(500);
// Присоединение в конец строки
bigNews.append("I've been working too hard.");
cout << bigNews << endl;
cout << "Size = " << bigNews.size() << endl;
cout << "Capacity = "
<< bigNews.capacity() << endl;
Результат:
Size = 22
Capacity = 31
I thought I saw Elvis in a UFO.
Size = 32
Capacity = 47
I thought I saw Elvis in a UFO. I've been working too hard.
Size = 59
Capacity = 511
Функция capacity() возвращает размер текущего блока памяти, выделенного для хранения данных строки (то есть количество символов, которое можно сохранить в строке без необходимости выделения дополнительной памяти).
Функция reserve() является средством оптимизации, выражающим ваше намерение зарезервировать определенный объект памяти для будущего использования; capacity() всегда возвращает значение, по крайней мере не меньшее того, которое было задано при последнем вызове reserve().
Функция resize() дополняет строку пробелами, если новый размер больше текущего, или усекает ее в противном случае. (Перегруженная версия resize() зволяет задать символ для дополнения строки).
Замена символов в строках
Функция вставки символов insert() очень удобна: вам не придется беспокоиться, чтобы
вставляемые символы не вышли за пределы текущего блока памяти. Строка расширяется и существующие символы вежливо подвигаются, уступая место новым. Впрочем, иногда такое поведение оказывается нежелательным.
начальную позицию в строке;
количество символов, заменяемых в исходной строке;
и строку замены (длина которой может не совпадать со вторым аргументом). Пример:
//: C03:StringReplace.cpp
// Простейший поиск с заменой в словах.
#include <cassert>
#include <string>
using namespace std;
string tag("$tag$");
s.insert(8, tag + ' ');
assert(s == "A piece $tag$ of text");
int start = s.find(tag);
assert(start == 8);
assert(tag.size() == 5);
s.replace(start, tag.size(), "hello there");
assert(s == "A piece hello there of text");
Результат:
//: C03:Replace.cpp
#include <cassert>
#include <cstddef> // Aey size_t
#include <string>
using namespace std;
// начиная с позиции 0.
size_t i = modifyMe.find(findMe, 0);
// Найдена ли заменяемая подстрока?
if (i != string::npos)
modifyMe.replace(i, findMe.size(), newChars);
"I thought I saw Elvis in a UFO. "
"I have been working too hard.";
string replacement("wig");
string findMe("UFO");
// Найти и заменить в bigNews подстроку "UFO":
replaceChars(bigNews, findMe, replacement);
assert(bigNews == "I thought I saw Elvis in a "
"wig. I have been working too hard.");
Результат:
// начиная с позиции 0.
size_t i = modifyMe.find(findMe, 0);
// Найдена ли заменяемая подстрока?
if (i != string::npos)
modifyMe.replace(i, findMe.size(), newChars);
//: C03:ReplaceAndGrow.cpp
#include <cassert>
#include <string>
using namespace std;
string replacement("yard shift.");
// Первый аргумент означает: "заменить символы
// за концом существующей строки":
bigNews.replace(bigNews.size() - 1,
replacement.size(), replacement);
assert(bigNews == "I have been working the "
"graveyard shift.");
Результат:
(АВ:Даже еще лучше - замена всех вхождений одной подстроки - другой подстрокой)
Замена вроде бы относится к делу, но возня с поиском, группами символов, позициями и тп, выглядит слишком сложно. Не позволяет ли класс string просто заменить один символ другим во всей строке?
//: C03:ReplaceAll.h
#ifndef REPLACEALL_H
#define REPLACEALL_H
#include <string>
const string& from, const string& to);
#endif // REPLACEALL_H ///:~
//: C03:ReplaceAll.cpp {O}
#include <cstddef>
#include "ReplaceAll.h"
using namespace std;
size_t foundHere;
while ((foundHere = context.find(from, lookHere))
!= string::npos) {
lookHere = foundHere + to.size();
return context;
Анализ:
//: C03:ReplaceAllTest.cpp
//{L} ReplaceAll
#include <cassert>
#include <iostream>
#include <string>
#include "ReplaceAll.h"
using namespace std;
replaceAll(text, "an", "XXX");
assert(text == "a mXXX, a plXXX, a cXXXal, pXXXama");
Результат:
для замены всех вхождений символа 'X' символом 'Y":
//: C03:StringCharReplace.cpp
#include <algorithm>
#include <cassert>
#include <string>
using namespace std;
replace(s.begin(), s.end(), 'X', 'Y');
assert(s == "aaaYaaaYYaaYYYaYYYYaaa");
Результат:
алгоритм replace() заменяет все вхождения одного символа другим символом.
Содержание