簡體   English   中英

如何在循環中處理迭代器?

[英]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::insertstring::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可以為空,在這種情況下我們只想刪除soldVal實例。

  • 此外,如果oldVal的大小大於s的返回值,也直接因為任何子字符串都比作為子字符串形式的字符串短。

  • 我只使用了一個for循環來檢查it是否是 one-past-end,在它的頭中它也不會增加it因為如果找到了oldVal那么它肯定會被newVal替換並因此更新和沒有必要提前它。 只有在s沒有找到oldVal我們才會推進它。

  • 此外,如果oldVal的大小大於迭代器s.end() - it范圍表示的子字符串s.end() - it在循環內將導致函數返回,這意味着它幾乎在s的末尾,我們沒有想要將它增加到最后一個元素。 這意味着it + oldVal.sizen位置的過尾迭代器,這意味着 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM