c語言入門到c++使用高手:深入學習C++之基本語言(九)

第二章 基本語言

第九節 迭代器精彩演繹,失效分析及彌補、實戰

1. 迭代器簡介

  • 迭代器是一種遍歷容器內元素的數據類型,這種數據類型有點像指針,我們理解爲迭代器用來指向容器中的某個元素

  • string, vector,[], 很少用[],更常用的訪問方式就是用迭代器(更通用)

  • 通過迭代器,我們可以讀容器中的元素值,讀string中的每個字符,還可以修改某個迭代器所指向的元素值

  • ++/–

  • list, map,儘量學會用迭代器來訪問容器中的元素

2. 容器的迭代器類型

 vector<int> vint{100, 200, 233};
    // 把整個vector<int>::iterator 理解成一個類型,這種類型專門應用於迭代器
    // 當我們用這個類型定義一個變量的時候,這個變量就是迭代器,這裏的iter這個變量就是個迭代器
    vector<int>::iterator iter;//定義迭代器,也必須是vector<int>
    

3. 迭代器begin()/end()操作,反向迭代器rbegin()/rend()操作

  • begin()/end()用來返回迭代類型, rbegin()/rend()也是用來返回迭代類型

  • begin():如果容器中有元素,則begin()返回的迭代器,指向的是容器的第一個元素

  • end():返回一個迭代器類型

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> vint{100, 200, 233};
    vector<int>::iterator iter;//定義迭代器


//    iter指向了vint[0]
    iter = vint.begin();
//    end返回的迭代器指向的並不是末端元素,而是末端元素後邊
//    可以理解成end()指向的是一個不存在的元素
    iter = vint.end();
    return 0;
}
  • 如果容器爲空的話,begin()和end()返回的迭代器相同
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> vint{};
    vector<int>::iterator iterb, itere;//定義迭代器
    iterb = vint.begin();
    itere = vint.end();
    if(iterb==itere){
        cout << "容器爲空!" << endl;
    }
    return 0;
}
  • 傳統寫法
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> vint{1, 3, 393};
    for (vector<int>::iterator iter = vint.begin(); iter != vint.end(); iter++) {
        cout << *iter << endl;
    }
  
    return 0;
}
  • 反向迭代器: 想從後往前遍歷一個容器,那麼使用反向迭代器就比較方便

  • rbegin():返回反向迭代器,返回反向迭代器的第一個元素

  • rend(): 返回一個反向迭代器,返回反向迭代器的最後元素的下一個位置

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> vint{1, 3, 393};
    for (vector<int>::reverse_iterator riter = vint.rbegin(); riter != vint.rend(); ++riter) {
        cout << *riter << endl;
    }
    return 0;
}

4. 迭代器運算符

  • *iter:返回迭代器iter所指向元素的引用,必須要保證這個迭代器指向的是有效的容器元素,
    不能指向end(),因爲end()是末端元素的後邊,即是一個不可控的位置

  • ++iter,iter++: 讓迭代器指向容器中的下一個元素,已經指向end()的時候,不能再進行自加

  • –iter, iter–:讓迭代器指向容器中的上一個元素,已經指向begin()的時候,不能再進行自減

  • iter1 == iter2, iter1 != iter2,判斷兩個迭代器是否相等,如果兩個迭代器指向的是同一個元素,就相等,否則就不相等

  • 容器中是結構體,如何引用結構中的成員

#include <iostream>
#include <string>
#include <vector>

using namespace std;

struct student {
    int num;
};

int main() {
    vector<student> sv;
    student stu;
    stu.num = 100;
    sv.push_back(stu);

    vector<student>::iterator iter;
    iter = sv.begin();
    cout << (*iter).num << endl;
    cout << iter->num << endl;
    return 0;
}

5. const_iterator迭代器

  • const: 表示常量

  • const_iterator迭代器,表示迭代器指向的元素值不能改變,指向的位置可以改變
    所以這個迭代器只能從容器中讀元素,不能改寫容器中的元素,更像一個常量指針

#include <iostream>
#include <string>
#include <vector>

using namespace std;


int main() {
    vector<int> iv{100, 200, 300};
    vector<int>::const_iterator citer;
    for(citer = iv.begin(); citer != iv.end(); citer++){
        // 會報錯
        //*citer = 1;
        cout << *citer <<endl;//可以正常讀
    }
    return 0;
}

5.1 cbegin()和cend()操作

  • c++ 引入的兩個新函數cbegin(),cend()跟begin()和end()類似,cbegin()和cend返回的都是常量迭代器
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    for( auto iter = iv.cbegin(); iter != iv.cend(); iter++){
        // 會報錯,因爲返回的是常量迭代器
        //*iter = 1;
        cout << *iter <<endl;//可以正常讀
    }
    return 0;
}

6. 迭代器失效

  • 在操作迭代器的過程中,不要改變容器的容量,也就是不要增加或者刪除vector容器中的元素

  • 在容器中增加或者從容器中刪除元素,這些操作可能會使指向容器元素的指針,引用,迭代器失效,
    失效就表示不能再代表容器中的元素,一旦使用失效的對象,程序就很容易崩潰

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    for (auto beg = iv.begin(); beg != iv.end(); beg++) {
        // 導致程序崩潰
        iv.push_back(1);
        cout << *beg << endl;
    }
    return 0;
}
  • 解決方案,如果一定要插入一個數據,加入break,然後重新開啓一個循環
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    for (auto beg = iv.begin(); beg != iv.end(); beg++) {
        // 導致程序崩潰
        iv.push_back(1);
        break;
    }
    for (auto beg = iv.begin(); beg != iv.end(); beg++) {
        cout << *beg <<endl;
    }
    return 0;
}

6.1 災難程序演示1

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    auto beg = iv.begin();
    auto end = iv.end();
    while (beg != end) {
        cout << *beg << endl;
        //插入新值
        //加入想往begin這個位置插入新值第一個參數爲插入的位置,第二個參數爲插入的元素
        //會導致迭代器失效,具體哪部分失效,依賴於容器的內部實現,最好的做法是break出來
        iv.insert(beg,88);
        break;//最明智的防止迭代器失效的方法,否則程序很可能崩潰
        ++beg;
    }

    auto beg1 = iv.begin();
    auto end1 = iv.end();
    while (beg1 != end1) {
        cout << *beg1 << endl;
        ++beg1;
    }


    return 0;
}
  • 保證迭代器不失效的代碼
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    auto beg = iv.begin();
    // 每次更新end(),防止end迭代器失效
    int icount = 0;
    while (beg != iv.end()) {
        // insert的返回結果用beg接收
        beg = iv.insert(beg, icount + 10);
        icount++;
        if (icount > 5) {
            break;
        }
        ++beg;
    }

    auto beg1 = iv.begin();
    auto end1 = iv.end();
    while (beg1 != end1) {
        cout << *beg1 << endl;
        ++beg1;
    }
    
    return 0;
}

6.2 災難程序演示2

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    //...
    for (auto iter = iv.begin(); iter != iv.end(); ++iter) {
        // earse函數,移除iter位置上的元素,返回下一個元素位置
        iv.erase(iter);//迭代器失效,導致程序崩潰
    }
    return 0;
}
  • 修改代碼
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    //...
    vector<int>::iterator iter = iv.begin();
    while(iter != iv.end()){
        iter = iv.erase(iter);
    }
    return 0;
}
  • 針對vector,一個一個刪除元素的方法
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv{100, 200, 300};
    while(!iv.empty()){
        auto beg = iv.begin();//因爲不爲空,所以返回begin()是沒問題的
        iv.erase(beg);//刪除該位值元素
    }
    return 0;
}

7. 範例演示

7.1 用迭代器遍歷一下string類型數據

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    string str{"I am Felaim!"};
    for (auto iter = str.begin(); iter != str.end(); ++iter) {
        *iter = toupper(*iter);
    }
    cout << str << endl;
    return 0;
}

7.2 vector容器常用操作與內存釋放

實戰程序

ServerName = 1//表示服務器名稱
ServerID =88 // 表示服務器ID
  • 完整實例
#include <iostream>
#include <string>
#include <vector>
#include <cstring>

using namespace std;
struct conf {
    char itemname[40];
    char itemcontent[100];
};


char *getinfo(vector<conf *> &conflist, char *pitem) {
    for (auto iter = conflist.begin(); iter != conflist.end(); ++iter) {
        if (strcmp((*iter)->itemname, pitem) == 0) {
            return (*iter)->itemcontent;
        }
    }
}

int main() {

    // new出來的內存要釋放!!!
    conf *pconf1 = new conf;
    strcpy(pconf1->itemname, "ServerName");
    strcpy(pconf1->itemcontent, "1區");

    conf *pconf2 = new conf;
    strcpy(pconf2->itemname, "ServerID");
    strcpy(pconf2->itemcontent, "1000");

    vector<conf *> conflist;
    conflist.push_back(pconf1);
    conflist.push_back(pconf2);

//    for (auto iter = conflist.begin(); iter != conflist.end(); ++iter) {
//        cout << (*iter)->itemname << endl;
//        cout << (*iter)->itemcontent << endl;
//    }

    char *p_tmp = getinfo(conflist, "ServerName");
    if (p_tmp != nullptr) {
        cout << p_tmp << endl;
    }

    // 需要釋放內存
    vector<conf *>::iterator pos;
    for (pos = conflist.begin(); pos != conflist.end(); ++pos) {
        //*pos纔是對應的指針,並沒有破壞迭代器,釋放的是迭代器指向的額外內存
        delete ((*pos));
    }
    conflist.clear();

    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章