如果你能为我解决一些困惑,我希望如此。我正在编写一个函数来删除字符串中的重复字符,例如“AB - >”AAABB“。
void remove_dups(std::string& str) {
std::string::iterator it = str.begin();
while (it != str.end()) {
if (*(it+1) == *it) {
str.erase(it+1);
} else {
++it;
}
}
}
我测试时似乎有效。但是,我想知道,不应该有栅栏问题吗?当'it'是字符串的结尾时,if语句会查看不存在的下一个字符。根据cplusplus.com,
过去的结尾字符是一个理论字符,它将跟随字符串中的最后一个字符。它不应被解除引用。 (http://www.cplusplus.com/reference/string/string/end/)
所以我想知道为什么我的函数似乎通过了测试,以及如何以优雅的方式重写整个事情来绕过fencepost问题。 (对我来说很容易,伙计。我是一个n00b。)
像
一样重写void remove_dups(std::string& str) {
std::string::iterator it = str.begin();
while (it != str.end()) {
if ((it+1) != str.end() && *(it+1) == *it) {
str.erase(it+1);
} else {
++it;
}
}
}
似乎不优雅。
答案 0 :(得分:2)
所以我想知道为什么我的功能似乎通过了测试
未定义的行为并不意味着它不会做你想做的事。它可能完全符合您的要求,只是出于错误的原因。我猜测未定义的行为表现为获取\0
字符,该字符不太可能与字符串中的其他字符进行比较。
以及如何以优雅的方式重写整个事情以解决围栏问题
有很多选择,但我的个人喜好是
if (it != str.end()) {
++it;
while (it != str.end()) {
/* compare *it and *(it-1) */
}
}
但要注意erase
必须移动元素。假设您总共删除了20个字符。你将字符串的其余部分移回一个字符20次。如果您稍微修改算法,则不需要这样做:
void remove_dups(std::string& str) {
std::string::iterator src = str.begin();
std::string::iterator dst = str.begin();
if (src != str.end()) {
++src;
++dst;
while (src != str.end()) {
if (*src != *(src-1)) {
*dst = *src;
++dst;
}
++src;
}
str.resize(dst - str.begin());
}
}
这种方法还有一个好处,即使erase
在内存中移动字符串,它也可以工作,这可能会导致迭代器中断。
答案 1 :(得分:1)
标准允许与最后一个数组元素进行比较,不允许取消引用它。因此,您可以比较其地址,以验证循环是否应该结束,但不允许您读取内容。由于迭代器主要是指针,因此该规则也适用于string,vector,...
另外,请注意string :: erase的非序列版本将迭代器返回到占用已删除元素的字符,您可以将其用作新的循环迭代器。
答案 2 :(得分:0)
我认为最干净的解决方案是停止在字符串的末尾(字符串的最后一个字符),因为在那时,无论如何都不会执行进一步的操作。
因此while (it != str.end())
应为while (it != str.end() && it != (str.end() - 1))
。