[英]How to safley deal with iterators in loops?
你好,我在 C++ 入門第 5 版中被要求“編寫一個函數,該函數采用三個字符串,s、oldVal 和 newVal。使用迭代器,插入和擦除函數將 s 中出現的所有 oldVal 實例替換為 newVal。測試你的函數通過使用它來替換常見的縮寫,例如“tho”替換為“though”,“thru”替換為“through”。”
我試過這個:
void set_str(string& s, const string& oldVal, const string& newVal) {
for (auto it = s.begin(); it != s.end(); ++it) {
auto it2 = oldVal.begin();
for (; it2 != oldVal.end() && *it == *it2; ++it, ++it2)
;
if (it2 == oldVal.end()) {
it = s.erase(it - (oldVal.size()), it);
it = s.insert(it, newVal.begin(), newVal.end());
it += newVal.size();
}
}
}
int main() {
string oldVal = "good";
string newVal = "gud";
string s = "C++ is really a good programming language, it is not only good in its performance \
but also good in dealing with real world problems";
set_str(s, oldVal, newVal);
cout << s << endl;
}
set_str
中操作迭代器是否有效,或者我是否正在觸發 UB? 謝謝!。 非常歡迎任何其他建議。這是輸出:
C++ is really a gud programming language, it is not only gud in its performance but also gud in dealing with real world problems
你很接近,但你還沒有完全到那里。
當oldVal
位於字符串末尾時會出現問題。 然后將it
放在修改后的字符串的末尾,然后在for
循環的末尾增加它。 結果:UB(我測試時出現分段錯誤)。 同樣的一比一錯誤意味着當輸入字符串中有多個匹配項時,邏輯不太正確。
幸運的是,解決方案很簡單。 只需更換:
it += newVal.size();
和
it += newVal.size() - 1;
現場演示(嘗試刪除 -1 並重新運行代碼)。
編輯:作為OP指出,還有一個問題,如果oldVal
是長於剩余的字符數s
,其余字符匹配s
。 為了解決這個問題,只需檢查oldVal
是否oldVal
都比s
剩余的字符數長,如果是,則退出:
for (auto it = s.begin(); it != s.end(); ++it) {
if (oldVal.length() > (size_t) (s.end() - it))
return;
...
編輯 #2:為了解決 oldVal 為空或 newVal 為空的問題,我真誠地希望這里是最終的、完全健壯的版本:
using namespace std;
void set_str(string& s, const string& oldVal, const string& newVal) {
if (oldVal.length() == 0)
return;
int delta_it;
for (auto it = s.begin(); it != s.end(); it += delta_it) {
if (oldVal.length() > (size_t) (s.end() - it))
return;
auto it2 = oldVal.begin();
for (; it2 != oldVal.end() && *it == *it2; ++it, ++it2)
;
delta_it = 1;
if (it2 == oldVal.end()) {
it = s.erase(it - (oldVal.size()), it);
it = s.insert(it, newVal.begin(), newVal.end());
delta_it = newVal.size();
}
}
}
不使用using namespace std
的通常警告適用。
在某些情況下,代碼具有未定義的行為。
第一個嵌套循環根據oldVal
的字符數遞增it
,但it
是來自完全不同的字符串 ( s
) 的迭代器。 因此,該循環可以it
到達並通過s
的末尾。
顯然,這不會在所有情況下都發生,但構造一個例子並不困難, it
會遞增直到它等於s.end()
並被取消引用。
使用索引實現此代碼比使用迭代器更容易和更安全,事實上,像string::insert
或string::erase
帶有索引的版本對於采用std::size_t count
的版本無關緊要,因為它保證執行它的工作到大小。 這意味着不要取消引用或索引不存在的元素。 但是,正如您使用迭代器所問的那樣,假設用戶的每一個可能輸入並確保不增加end
或減少begin
迭代器,這有點復雜。
我已經看到了“C++ 5ed”練習的答案,但在某些情況下它也會導致 UB。
但是,我已經嘗試為您處理最可能的輸入字符串情況詳細說明一個示例:
void set_str(string& s, const string& oldVal, const string& newVal) {
if (s.empty() || oldVal.empty() || oldVal.size() > s.size())
return;
for (auto it = s.begin(); it != s.end(); ) {
if (oldVal.size() > (size_t)(s.end() - it) )
return ;
if (string(it, it + oldVal.size()) == oldVal) {
cout << "found: " << (it - s.begin()) << endl;
it = s.erase(it, it + oldVal.size());
it = s.insert(it, newVal.begin(), newVal.end());
it += newVal.size();
}
else
++it;
}
}
正如您在第一行中看到的那樣,我們檢查s
是否為空,因此無需執行其余操作,因此如果oldVal
為空,則直接返回也無需繼續,這意味着無需繼續。 但是newVal
可以為空,在這種情況下我們只想刪除s
的oldVal
實例。
此外,如果oldVal
的大小大於s
的返回值,也直接因為任何子字符串都比作為子字符串形式的字符串短。
我只使用了一個for
循環來檢查it
是否是 one-past-end,在它的頭中它也不會增加it
因為如果找到了oldVal
那么它肯定會被newVal
替換並因此更新和沒有必要提前它。 只有在s
沒有找到oldVal
我們才會推進它。
此外,如果oldVal
的大小大於迭代器s.end() - it
范圍表示的子字符串s.end() - it
在循環內將導致函數返回,這意味着它幾乎在s
的末尾,我們沒有想要將它增加到最后一個元素。 這意味着it + oldVal.size
是n
位置的過尾迭代器,這意味着 UB。 這確保it
應該在s.end() - oldVal.size()
位置停止前進。
有趣的是我沒有使用內部循環從s
開始構建一個子字符串,從it
表示的位置到 it + oldVal.size()
而是我們從迭代器表示的字符范圍創建一個臨時字符串it, it + oldVal.size()
from s
然后與oldVal
比較:
如果臨時字符串等於oldVal
那么我們將它從s
( it, it + oldVal.size()
) 中刪除並更新it
以定位擦除后的第一個字符。
我們在那個位置之前插入newVal
然后我們增加it
newVal
的位置。 newVal
是否為空並不重要。
否則,如果在s
沒有找到oldVal
,我們只需增加it
。
記住這一點:當且僅當沒有找到oldVal
,我們才會增加it
,這意味着沒有擦除或插入,因此它沒有失效。 如果找到,我們將處理它的定位,但不會在for-loop
再次增加它
現在是時候搞亂驅動程序中的set_str
了:
int main() {
string oldVal = "a";
string newVal = "";
string s = "aa";
set_str(s, oldVal, newVal);
std::cout << "s: " << s << std::endl;
}
為s, oldVal, newVal
嘗試盡可能多的輸入,看看它是否會導致 UB。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.