#c #string #iterator
#c #строка #итератор
Вопрос:
Я знаю, что я не первый, кто поднимает проблему с обратными итераторами, пытающимися вызвать метод erase () для строк. Однако я не смог найти никаких хороших способов обойти это.
Я читаю содержимое файла, который содержит кучу слов. Когда я читаю в word, я хочу передать его функции, которую я назвал stripPunct . Однако я хочу убрать знаки препинания только в начале и конце строки, а не в середине.
Так, например:
(word) должен удалить ‘(‘ и ‘)’, в результате чего останется только word
ненадо! следует удалить ‘!’, в результате чего просто не
Итак, моя логика (которую, я уверен, можно было бы улучшить) заключалась в том, чтобы иметь два цикла while, один начинающийся в конце, а другой в начале, проходящий и стирающий, пока не достигнет символа без знаков препинания.
void stripPunct(string amp; str) {
string::iterator itr1 = str.begin();
string::reverse_iterator itr2 = str.rbegin();
while ( ispunct(*itr1) ) {
str.erase(itr1);
itr1 ;
}
while ( ispunct(*itr2) ) {
str.erase(itr2);
itr2--;
}
}
Однако, очевидно, что это не работает, потому что erase () требует обычного итератора, а не reverse_iterator . Но в любом случае, я чувствую, что эта логика довольно неэффективна.
Кроме того, я попытался вместо reverse_iterator использовать обычный итератор, запустив его в str.end(), затем уменьшил его, но он говорит, что я не смогу разыменовать итератор, если я запущу его в str.end () .
Кто-нибудь может мне помочь с хорошим способом сделать это? Или, может быть, указать обходной путь для того, что у меня уже есть?
Заранее большое вам спасибо!
—————— [ ПРАВИТЬ ] —————————-
найдено решение, хотя оно может быть не лучшим решением:
// Call the stripPunct method:
stripPunct(str);
if ( !str.empty() ) { // make sure string is still valid
// perform other code
}
И вот метод stripPunct:
void stripPunct(string amp; str) {
string::iterator itr1 = str.begin();
string::iterator itr2 = str.end();
while ( !(str.empty()) amp;amp; ispunct(*itr1) )
itr1 = str.erase(itr1);
itr2--;
if ( itr2 != str.begin() ) {
while ( !(str.empty()) amp;amp; ispunct(*itr2) ) {
itr2 = str.erase(itr2);
itr2--;
}
}
}
Комментарии:
1. Что вы, безусловно, делаете неправильно, так это то, что вы используете
itr1
иitr2
после того, как вы их удалили, вы должны заменить их значением, возвращаемымerase
.2. Забыл об этом, спасибо, что указали на это!
Ответ №1:
Во-первых, обратите внимание на пару проблем с вашим кодом:
- после того, как вы вызываете,
erase()
используяitr1
, вы аннулируетеitr2
. - при использовании
reverse_iterator
для обратного прохождения последовательности вы хотите использовать--
(это своего рода причина существования обратных итераторов).
Теперь, чтобы улучшить логику, вы можете избежать стирания каждого символа по отдельности, найдя первый символ, который вы не хотите стирать, и стереть все до этого момента. find_if()
может быть использовано, чтобы помочь с этим:
int not_punct(char c) {
return !ispunct((unsigned char) c);
}
void stripPunct(string amp; str) {
string::iterator itr = find_if( str.begin(), str.end(), not_punct);
str.erase( str.begin(), itr);
string::reverse_iterator ritr = find_if( str.rbegin(), str.rend(), not_punct);
str.erase( ritr.base(), str.end());
}
Обратите внимание, что я использовал base()
для получения «обычного» итератора, соответствующего reverse_iterator
. Я нахожу логику того, base()
нужно ли ее корректировать, запутанной (обратные итераторы вообще меня смущают) — в данном случае это не так, потому что мы хотим начать стирание после найденного символа.
Эта статья Скотта Мейерса,http://drdobbs.com/cpp/184401406 , имеет хорошую обработку reverse_iterator::base()
в разделе. «Руководство 3: Понять, как использовать базовый итератор reverse_iterator». Информация из этой статьи также была включена в книгу Мейера «Эффективный STL».
Комментарии:
1. Майкл, спасибо, это выглядит полезным и является лучшим способом выполнения задач. Рассмотрим это. Я также опубликовал неаккуратное решение, которое я придумал в своем первоначальном вопросе.
2. @Rhinoo: обратите внимание, что я избавился от
if
тестов послеfind_if()
: они не нужны и фактически предотвратят обрезку строки, содержащей только знаки препинания, до пустой строки.3. Также обратите внимание, что вместо того, чтобы придумывать небольшую функцию-оболочку для передачи
find_if()
, вы можете быть более похожими на STL и использовать функции-помощники объекта из заголовка<functional>
: вместоnot_punct
передачиnot1(ptr_fun<int, int>(ispunct))
Однако, я, честно говоря, чаще всего нахожу такого рода вещи менее читаемыми.
Ответ №2:
Вы не можете разыменовать iterator::end(), потому что он указывает на недопустимую память (память сразу после конца массива), поэтому сначала вам нужно уменьшить ее.
И последнее замечание: если word состоит только из знаков препинания, ваша программа завершится сбоем, обязательно с этим разберитесь.
Комментарии:
1. Во-первых, да, я знаю, я сначала уменьшаю его, мой компилятор (visual studio 2010) все еще выдает мне эту ошибку. Извините, я должен был указать это в своем вопросе. Во-вторых, я обрабатываю случай ошибки, просто не поместил его в код в моем вопросе.
2. Я думаю, что я получил эту ошибку из-за того, что Маттео Италия сказал выше, я забыл переназначить итераторы.
3. Нет, он по-прежнему выдает мне «строковый итератор, не разыменуемый» для itr2, если я использую обычный итератор, установите для него значение str.end (), а затем сначала уменьшите его.
4. Я рассмотрю это завтра снова, видимо, я слишком сонный, чтобы нормально думать.
5. Эрбурет, спасибо, что вообще мне помог. И на самом деле, это было не так. Где-то я не обрабатываю случай ошибки, но это работает для некоторых строк. Еще раз спасибо!
Ответ №3:
Если вы не возражаете против отрицательной логики, вы можете сделать следующее:
string tmp_str="";
tmp_str.reserve(str.length());
for (string::iterator itr1 = str.begin(); itr1 != str.end(); itr1 )
{
if (!ispunct(*itr1))
{
tmp_str.push_back(*itr1);
}
}
str = tmp_str;