Назад | Начало урока | Вперед
Содержание

Глава 3

Вверх

Операции со строками

Каждому программисту с опытом работы на С хорошо знакомы функции записи, поиска, модификации и копирования массивов char. У функций стандартной библиотеки С имеются два недостатка.

Во первых они неформально делятся на две категории: "простые" функции и те, которым при вызове необходимо передать количество символов, участвующих в выполняемой операции. Список библиотечных функций С для работы с символьными массивами поражает неопытного пользователя: перед ним оказывается длинный перечень загадочных и неудобопроизносимых имен функций. Типы и количество аргументов у этих функций более или менее выдержаны, но для их правильного использования необходимо тщательно следить за именами и передачей параметров.

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

Строковые объекты С++ существенно превосходят свои прототипы С по удобству и безопасности. Количество имен функций класса string примерно соответствует количеству функций в библиотеке С, но механизм перегрузки существенно расширяет их функциональность. В сочетании с разумными правилами выбора имен и выбором аргументов по умолчанию, работать с классом string гораздо проще и удобнее, чем с функциями символьных массивов стандартной библиотеки С.

Вверх

Присоединение, вставка и конкатенация строк
append(),insert()

Одно из самых ценных и удобных свойств строк С++ состоит в том, что они автоматически растут по мере надобности, не требуя вмешательства со стороны программиста. Работа со строками не только становится более надежной, из нее почти полностью устраняются "нетворческие" операции - отслеживание границ памяти, в которой хранятся данные строки.

Например, если при создании строковый объект был инициализирован 50 экземплярами символа "X", а позднее в нем были сохранены 50 экзнемпляров строки "Zowie", объект сам выделит достаточный блок памяти в соответствии с увеличившимся объемом данных.

Но в полной мере это свойство проявляется в ситуациях, когда обрабатываемые в программе строки изменяются в размерах, но вы не можете оценить эти изменения количественно.

Строковые функции append() и insert() автоматически перераспределяют память при увеличении строки:


//: C03:StrSize.cpp
#include <string>
#include <iostream>
using namespace std;

int main() {

string bigNews("I saw Elvis in a UFO. ");
cout << bigNews << endl;
// Сколько данных фактически получено?
cout << "Size = " << bigNews.size() << endl;
// Сколько данных можно сохранить без перераспределения памяти?
cout << "Capacity = "
<< bigNews.capacity() << endl;

// Вставка строки в bigNews в позицию перед bigNews[1]
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;

} ///:~


Результат:

I saw Elvis in a UFO.
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

Анализ:

Из приведенного примера видно, что хотя вы снимаете с себя большую часть ответственности за выделение памяти и управление ею в строках, класс string предоставляет в ваше распоряжение ряд средств для контроля за их размером. Обратите внимание как легко был изменен размер блока памяти, выделенного под хранение символов строки.

Функция size() возвращает текущее количество символов, она идентична функции length().
Функция capacity() возвращает размер текущего блока памяти, выделенного для хранения данных строки (то есть количество символов, которое можно сохранить в строке без необходимости выделения дополнительной памяти).
Функция reserve() является средством оптимизации, выражающим ваше намерение зарезервировать определенный объект памяти для будущего использования; capacity() всегда возвращает значение, по крайней мере не меньшее того, которое было задано при последнем вызове reserve().
Функция resize() дополняет строку пробелами, если новый размер больше текущего, или усекает ее в противном случае. (Перегруженная версия resize() зволяет задать символ для дополнения строки).

Вверх

Замена символов в строках
Функция вставки символов insert() очень удобна: вам не придется беспокоиться, чтобы вставляемые символы не вышли за пределы текущего блока памяти. Строка расширяется и существующие символы вежливо подвигаются, уступая место новым. Впрочем, иногда такое поведение оказывается нежелательным.

Если вы хотите, чтобы существующие символы были заменены новыми, воспользуйтесь функцией перезаписи replace(). Существует несколько перегруженных версий replace(), но простейшая форма получает три аргумента:
начальную позицию в строке;
количество символов, заменяемых в исходной строке;
и строку замены (длина которой может не совпадать со вторым аргументом). Пример:


//: C03:StringReplace.cpp
// Простейший поиск с заменой в словах.
#include <cassert>
#include <string>
using namespace std;

int main() {

string s("A piece of text");
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");
} ///:~


Результат:

A piece hello there of text

Анализ:

Строка tag сначала вставляется в s (обратите внимание: вставка производится перед заданной позицией, и после tag еще вставляется дополнительный пробел). Далее выполняются операции поиска и замены.

АВ: В данной программе в третий аргумент функции replace() передается строка, большая по размеру, чем та, которую надо заменить. Тем не менее убирается при замене пять символов, а на их место вставляется строка из 8(!) символов. То есть три символа заменили на 8 символов, часть строки, оставшаяся после вставки просто подвинулась!

Прежде, чем вызвать replace(), стоит проверить удалось ли найти искомую подстроку. В предыдущем примере для замены использовался тип char*, но существует перегруженная версия replace() с аргументом типа string. Далее приводится более полный пример с заменой подстроки:

Вверх

Функция замены одной подстроки - другой подстрокой:


//: C03:Replace.cpp
#include <cassert>
#include <cstddef> // Aey size_t
#include <string>
using namespace std;

void replaceChars (string& modifyMe, const string& findMe, const string& newChars) {

// Найти в modifyMe подстроку findMe
// начиная с позиции 0.
size_t i = modifyMe.find(findMe, 0);
// Найдена ли заменяемая подстрока?
if (i != string::npos)
// Заменить найденную подстроку содержимым newChars
modifyMe.replace(i, findMe.size(), newChars);
}

int main() {

string bigNews =
"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.");
} ///:~


Результат:

I thought I saw Elvis in a wig. I have been working too hard.

Анализ:

АВ: То, что мы делали в предыдущей программе, в этой программе оформлено в виде функции:

void replaceChars (string& modifyMe, const string& findMe, const string& newChars) {

// Найти в modifyMe подстроку findMe
// начиная с позиции 0.
size_t i = modifyMe.find(findMe, 0);
// Найдена ли заменяемая подстрока?
if (i != string::npos)
// Заменить найденную подстроку содержимым newChars
modifyMe.replace(i, findMe.size(), newChars);
}

Если функция replace() не находит искомую подстроку, она возвращает string::npos - статическую константу класса string, которая представляет несуществующую позицию символа. (Наибольшее значение, которое по умолчанию может быть представлено для типа size_type строкового распределителя памяти (std::size_t по умолчанию))

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

АВ: то есть функция replace() заменяет одну последовательность символов другой последовательностью символов начиная с определенной позиции. И только если новая строка превосходит по размеру исходную, то увеличивает размер строки и вставляет все новые символы. А функция insert() деликатно сдвигает символы исходной строки, а на освободившееся место ставит новые символы. Размер новой строки естественно сразу увеличивается по заданному компилятором алгоритму.

Пример:

Вверх


//: C03:ReplaceAndGrow.cpp
#include <cassert>
#include <string>
using namespace std;

int main() {

string bigNews("I have been working the grave.");
string replacement("yard shift.");
// Первый аргумент означает: "заменить символы
// за концом существующей строки":
bigNews.replace(bigNews.size() - 1, replacement.size(), replacement);
assert(bigNews == "I have been working the "
"graveyard shift.");
} ///:~


Результат:

I have been working the graveyard shift.

Анализ:

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

Возможно вы наскоро просматриваете эту главу в поисках рецепта для выполнения простой операции, такой как замена всех вхождений одного символа другим символом.
(АВ:Даже еще лучше - замена всех вхождений одной подстроки - другой подстрокой)
Замена вроде бы относится к делу, но возня с поиском, группами символов, позициями и тп, выглядит слишком сложно. Не позволяет ли класс string просто заменить один символ другим во всей строке?

Такую функцию легко написать на базе функций find() и replace().

Вверх

Функция замены всех вхождений одной подстроки - другой подстрокой:


//: C03:ReplaceAll.h
#ifndef REPLACEALL_H
#define REPLACEALL_H
#include <string>

std::string& replaceAll(string& context,
const string& from, const string& to);
#endif // REPLACEALL_H ///:~

//: C03:ReplaceAll.cpp {O}
#include <cstddef>
#include "ReplaceAll.h"
using namespace std;

string& replaceAll(string& context, const string& from, const string& to) {

size_t lookHere = 0;
size_t foundHere;
while ((foundHere = context.find(from, lookHere)) != string::npos) {
context.replace(foundHere, from.size(), to);
lookHere = foundHere + to.size();
}
return context;
} ///:~


Анализ:

Версия find(), использованная в этой программе, получает во втором аргументе начальную позицию поиска, и возвращает string::npos. Позицию, хранящуюся в переменной lookHere, важно сместить за строку замены на тот случай, если from является подстрокой to.

Следующая программа предназначена для тестирования функции replaceAll():


//: C03:ReplaceAllTest.cpp
//{L} ReplaceAll
#include <cassert>
#include <iostream>
#include <string>
#include "ReplaceAll.h"
using namespace std;

int main() {

string text = "a man, a plan, a canal, panama";
replaceAll(text, "an", "XXX");
assert(text == "a mXXX, a plXXX, a cXXXal, pXXXama");
} ///:~


Результат:

a mXXX, a plXXX, a cXXXal, pXXXama

Анализ:

Как видите класс string сам по себе не решает все возможные задачи - во многих решениях приходится привлекать алгоритмы стандартной библиотеки (подробнее в гл 6), поскольку класс string может рассматриваться как контейнер STL ( для чего используются итераторы, о которых говорилось выше).

Все общие алгоритмы работают с "интервалами" элементов, хранящихся в контейнерах. Чаще всего используются интервалы "от начала до конца" контейнера. Объект string интерпретируется как контейнер с элементами-символами; начало интервала определяется итератором string::begin(), а конец интервала - итератором string::end().

Вверх

Следующий пример демонстрирует применение алгоритма replace()
для замены всех вхождений символа 'X' символом 'Y":


//: C03:StringCharReplace.cpp
#include <algorithm>
#include <cassert>
#include <string>
using namespace std;

int main() {

string s("aaaXaaaXXaaXXXaXXXXaaa");
replace(s.begin(), s.end(), 'X', 'Y');
assert(s == "aaaYaaaYYaaYYYaYYYYaaa");
} ///:~


Результат:

Анализ:

Обратите внимание алгоритм replace() вызывается не как функция класса string.

Кроме того, в отличие от функций string::replace(), выполняющих только одну замену,
алгоритм replace() заменяет все вхождения одного символа другим символом.

Алгоритм replace() работает только с отдельными объектами ( в данном случае с объектами char), он не может использоваться для замены символьных массивов или объектов string. Поскольку объект string ведет себя как контейнер STL, к нему могут применяться и другие алгоритмы. Это позволяет решать задачи, не решаемые напрямую функциями класса string.


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz