C++ stl容器詳解

漲月薪發表於2019-03-29

STL(標準模板庫),是目前C++內建支援的library。它的底層利用了C++類别範本和函式模板的機制,由三大部分組成:容器、演算法和迭代器。

一、容器是STL中很重要的一種資料結構。常見的容器包括

1.vector容器

vector就是動態陣列。在堆中分配記憶體,元素連續存放,有保留記憶體,如果減少大小後,記憶體也不會釋放。

如果新值>當前大小時才會再分配記憶體。

它擁有一段連續的記憶體空間,並且起始地址不變,因此它能非常好的支援隨即存取,即[]操作符,但由於它的記憶體空間是連續的,所以在中間進行插入和刪除會造成記憶體塊的拷貝。

當該陣列後的記憶體空間不夠時,需要重新申請一塊足夠大的記憶體並進行記憶體的拷貝。這些都大大影響了vector的效率。底層資料結構為陣列 ,支援快速隨機訪問。

【vector總結】

需要經常隨機訪問請用vector

【vector的基本用法】

front()返回頭部元素的引用,可以當左值

back()返回尾部元素的引用,可以當左值

push_back()新增元素,只能尾部新增

pop_back()移除元素,只能在尾部移除

int main(int argc, const char * argv[]) {
    
    //定義一個vector容器
    vector<int> v1;
    
    //插入元素(尾部插入)
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    
    //迭代器遍歷列印
    for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    //修改頭部元素的值(front()返回是引用,可以當左值)
    v1.front() = 44;
    
    //輸出頭部元素
    cout<< "頭部元素:" << v1.front() << endl;
    
    //修改尾部的值(back()返回是引用,可以當左值)
    v1.back() = 99;
    
    //輸出尾部元素
    cout << "尾部元素" << v1.back() <<endl;
    
    //刪除元素(從尾部刪除)
    v1.pop_back();
    
    //迭代器遍歷列印
    for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}
複製程式碼

【vector的初始化】

vector有4種方式初始化,有直接初始化,也有通過拷貝建構函式初始化。

int main(int argc, const char * argv[]) {
    //直接建構函式初始化
    vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    
    //通過拷貝建構函式初始化
    vector<int> v2 = v1;
    
    //使用部分元素來構造
    vector<int> v3(v1.begin(), v1.begin() + 1);
    vector<int> v4(v1.begin(), v1.end());
    
    //存放三個元素,每個元素都是9
    vector<int> v5(3,9);
    
    return 0;
}
複製程式碼

【vector的遍歷】

vector的遍歷有多種方式,可以根據[]或者迭代器遍歷。

  • []方式,如果越界或出現其他錯誤,不會丟擲異常,可能會崩潰,可能資料隨機出現

  • at方式,如果越界或出現其他錯誤,會丟擲異常,需要捕獲異常並處理

  • 迭代器提供了逆向遍歷,可以通過迭代器來實現逆向遍歷,當然上面兩種方式也可以

int main(int argc, const char * argv[]) {
    
    //建立vector
    vector<int> v1;
    
    //插入元素
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
    }
    
    //遍歷-[]取值
    for (int i = 0; i < v1.size(); i++) {
        cout << v1[i] << " ";
    }
    cout << endl;
   
    //遍歷-at取值
    for (int i = 0; i < v1.size(); i++) {
        cout << v1.at(i) << " ";
    }
    cout << endl;

    //遍歷-迭代器遍歷
    for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    //遍歷-迭代器逆向遍歷
    for (vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    //測試越界
    cout << "[]越界:" << v1[20] << endl;      //不會丟擲異常,可能會崩潰,可能會亂碼
    cout << "at越界:" << v1.at(20) << endl;   //會丟擲異常,需要捕獲異常
    
    return 0;
}
複製程式碼

【vector的push_back強化】

push_back是在當前vector的記憶體末尾拷貝元素進入容器。注意這個地方可能產生淺拷貝,所以容器中的物件要支援拷貝操作。另外,如果vector初始化了個數,而不初始化具體的值,push_back也只會在最後面追加。

int main(int argc, const char * argv[]) {
    //初始化10個元素的容器
    vector<int> v(10);
    
    //列印容器大小
    cout << v.size() << endl;
    
    //push_back新增元素
    v.push_back(100);
    
    //列印容器大小
    cout << v.size() << endl;
    
    //遍歷後的結果是  0 0 0 0 0 0 0 0 0 0 100
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}
複製程式碼

【vector的元素刪除】

vector的刪除,是根據位置進行刪除,如果想要刪除某個元素,需要找到當前元素的迭代器位置,再進行刪除。

erase(iterator)函式,刪除後會返回當前迭代器的下一個位置。

int main(int argc, const char * argv[]) {
    //1 建立容器並初始化
    vector<int> v1(10);
    for (int i = 0; i < v1.size(); i++) {
        v1[i] = i;
    }
    
    //2 區間刪除
    //--2.1 刪除前3個元素
    v1.erase(v1.begin(), v1.begin() + 3);
    
    //--2.2 刪除指定位置的元素
    v1.erase(v1.begin() +3);
    
    //3 根據元素的值進行刪除,刪除值為2的元素
    v1.push_back(2);
    v1.push_back(2);
    vector<int>::iterator it = v1.begin();
    while (it != v1.end()) {
        if (*it == 2) {
            it = v1.erase(it);   //刪除後,迭代器指標會執行下一個位置並返回。
        }else{
            it++;
        }
    }
    
    //4 遍歷列印
    for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}
複製程式碼

【vector的插入元素】

vector提供了insert函式,結合迭代器位置插入指定的元素。如果迭代器位置越界,會丟擲異常。

int main(int argc, const char * argv[]) {
    //初始化vector物件
    vector<int> v1(10);
    
    //在指定的位置插入元素10的拷貝
    v1.insert(v1.begin() + 3, 10);
    
    //在指定的位置插入3個元素11的拷貝
    v1.insert(v1.begin(), 3, 11);
    
    //遍歷
    for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}
複製程式碼

2.list連結串列模型

list就是雙向連結串列,在堆中存放,每個元素都是放在一塊記憶體中,它的記憶體空間可以是不連續的,通過指標來進行資料的訪問,這個特點使得它的隨機存取變的非常沒有效率,因此它沒有提供[]操作符的過載。

但它可以以很好的效率支援任意地方的刪除和插入。

list沒有空間預留習慣,所以每分配一個元素都會從記憶體中分配,每刪除一個元素都會釋放它佔用的記憶體.底層資料結構為雙向連結串列,支援快速增刪

【list總結】

如果你喜歡經常新增刪除大物件的話,那麼請使用list 要儲存的物件不大,構造與析構操作不復雜,那麼可以使用vector代替 list<指標>完全是效能最低的做法,這種情況下還是使用vector<指標>好,因為指標沒有構造與析構,也不佔用很大記憶體

【list的基本操作】

int main(int argc, const char * argv[]) {
    //建立list物件
    list<int> l;
    
    //尾部新增元素
    for (int i = 0; i < 10; i++) {
        l.push_back(i);
    }
    
    //頭部新增元素
    l.push_front(111);
    
    //遍歷
    for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    //list不能隨機訪問
    list<int>::iterator it = l.begin();
    it++;
    it++;
    cout << *it <<endl;
//    it = it + 1;  //編譯報錯,不能隨機訪問
//    it = it + 5;  //編譯報錯,不能隨機訪問
    return 0;
}
複製程式碼

【list的刪除】

list提供了兩個函式用來刪除元素,分別是erase和remove。

erase是通過位置或者區間來刪除,主要結合迭代器指標來操作

remove是通過值來刪除

int main(int argc, const char * argv[]) {
    //建立list物件
    list<int> l;
    
    //新增資料
    for (int i = 0; i < 10; i++) {
        l.push_back(i);
    }
    l.push_back(100);
    l.push_back(100);
    
    //刪除頭部元素
    l.erase(l.begin());
    
    //刪除某個區間
    list<int>::iterator it = l.begin();
    it++;
    it++;
    it++;
    l.erase(l.begin(), it);
    
    //移除值為100的所有元素
    l.remove(100);
    
    //遍歷
    for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}
複製程式碼

3.deque雙端陣列

[堆1] --> [堆2] -->[堆3] -->...

每個堆儲存好幾個元素,然後堆和堆之間有指標指向,看起來像是list和vector的結合品.

它支援[]操作符,也就是支援隨機存取,可以讓你在前面快速地新增刪除元素,或是在後面快速地新增刪除元素,然後還可以有比較高的隨機訪問速度,和vector的效率相差無幾。

它支援在兩端的操作:push_back,push_front,pop_back,pop_front等,並且在兩端操作上與list的效率也差不多。

在標準庫中vector和deque提供幾乎相同的介面,在結構上它們的區別主要在於這兩種容器在組織記憶體上不一樣,deque是按頁或塊來分配儲存器的,每頁包含固定數目的元素.相反vector分配一段連續的記憶體,vector只是在序列的尾段插入元素時才有效率,而deque的分頁組織方式即使在容器的前端也可以提供常數時間的insert和erase操作,而且在體積增長方面也比vector更具有效率

【deque操作】

  • push_back 從尾部插入元素
  • push_front 從頭部插入元素
  • pop_back 從尾部刪除元素
  • pop_front 從頭部刪除元素
  • distance函式可以求出當前的迭代器指標it距離頭部的位置,也就是容器的指標,用法: distance(v1.begin(), it)
int main(int argc, const char * argv[]) {
    //定義deque物件
    deque<int> d1;
    
    //尾部插入元素
    d1.push_back(10);
    d1.push_back(20);
    d1.push_back(30);
    
    //頭部插入元素
    d1.push_front(1);
    d1.push_front(2);
    d1.push_front(3);
    
    //尾部刪除元素
    d1.pop_back();
    
    //頭部刪除元素
    d1.pop_front();
    
    //修改頭部和尾部的值
    d1.front() = 111;
    d1.back()  = 222;
    
    //查詢元素為1的下標
    //通過distance求取下標
    deque<int>::iterator it = d1.begin();
    while (it != d1.end()) {
        if (*it == 1) {
            cout << "下標:" << distance(d1.begin(), it) << endl;
        }
        it++;
    }
    
    //遍歷
    for (deque<int>::iterator it = d1.begin(); it != d1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}
複製程式碼

【vector、list、deque總結】

vector是可以快速地在最後新增刪除元素,並可以快速地訪問任意元素

list是可以快速地在所有地方新增刪除元素,但是隻能快速地訪問最開始與最後的元素

deque在開始和最後新增元素都一樣快,並提供了隨機訪問方法,像vector一樣使用[]訪問任意元素,但是隨機訪問速度比不上vector快,因為它要內部處理堆跳轉 deque也有保留空間.另外,由於deque不要求連續空間,所以可以儲存的元素比vector更大,這點也要注意一下.還有就是在前面和後面新增元素時都不需要移動其它塊的元素,所以效能也很高。

因此在實際使用時,如何選擇這三個容器中哪一個,應根據你的需要而定,一般應遵循下面的原則:

1、如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector

2、如果你需要大量的插入和刪除,而不關心隨即存取,則應使用list

3、如果你需要隨即存取,而且關心兩端資料的插入和刪除,則應使用deque。


4.stack棧模型

底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時

▽ 基礎資料型別的stack

int main(int argc, const char * argv[]) {
    
    //定義stack物件
    stack<int> s1;
    
    //入棧
    s1.push(1);
    s1.push(2);
    s1.push(3);
    s1.push(4);
    
    //列印棧頂元素,並出棧
    while (!s1.empty()) {
        //取出棧頂元素
        cout << "當前棧頂元素" << s1.top() << endl;
        
        //獲取棧的大小
        cout << "當前棧的大小" << s1.size() << endl;
        
        //出棧
        s1.pop();
    }
    
    return 0;
}
複製程式碼

▽ 複雜資料型別的stack

//定義類
class Teacher {
    
public:
    
    char name[32];
    int  age;
    
    void printT()
    {
        cout << "age = " << age << endl;
    }
    
};

int main(int argc, const char * argv[]) {
    
    Teacher t1, t2, t3;
    t1.age = 22;
    t2.age = 33;
    t3.age = 44;
    
    //定義棧容器
    stack<Teacher> s1;
    
    //入棧
    s1.push(t1);
    s1.push(t2);
    s1.push(t3);
    
    //出棧並列印
    while (!s1.empty()) {
        //列印棧頂元素
        Teacher tmp = s1.top();
        tmp.printT();
        
        //出棧
        s1.pop();
    }

    return 0;
}
複製程式碼

5.queue佇列模型

底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時 (stack和queue其實是介面卡,而不叫容器,因為是對容器的再封裝)

#include <queue>
void main()
{
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);
    
    cout << "對頭元素" << q.front() <<endl;
    cout << "佇列的大小 " << q.size() <<endl;
    
    while (!q.empty()) {
        int tmp = q.front();
        cout << tmp << " ";
        q.pop();
    }
}

class Teacher
{
publicint age;
    char name[32];
    
    void printT()
    {
        cout << "age :" << age <<endl;
    }
}

void main()
{
    Teacher t1,t2,t3;
    t1.age = 31;
    t2.age = 32;
    t3.age = 33;
    
    queue<Teacher > q;
    q.push(t1);
    q.push(t2);
    q.push(t3);
    
    while (!q.empty()) {
        Teacher tmp = q.front();
        tmp.printT();
        q.pop();
    }
}
複製程式碼

6.priotriy_queue優先順序佇列容器

底層資料結構一般為vector為底層容器,堆heap為處理規則來管理底層容器實現

優先順序佇列分為:最小值優先佇列和最大值優先佇列。

此處的最大值、最小值是指隊頭的元素(增序、降序)。預設,是建立最大值優先順序佇列。 定義優先順序的方法:

priority_queue預設定義int型別的最大值佇列

priority_queue<int, vector, less>定義int型的最大值優先佇列

priority_queue<int, vector, greater>定義int型的最小值佇列

上面的定義中,less和greater相當於謂詞,是預定義好的排序函式,稱之為“仿函式”。

void main()
{
    //定義優先順序佇列(預設是最大值優先順序佇列)
    priority_queue<int> p1;
    
    //定義一個最大優先順序佇列
    //less是提前定義好的預定義函式 相當於謂詞
    priority_queue<int, vector<int>, less<int>> p2;
    
    //定義一個最小值優先順序佇列v
    priority_queue<int, vector<int>, greater<int>> p3;
    
    //給預設的最大優先順序佇列入棧
    p1.push(33);
    p1.push(11);
    p1.push(55);
    p1.push(22);
    
    //列印最大優先順序的對頭元素
    cout<<"對頭元素:"<< p1.top() <<endl;
    cout<<"佇列的大小:"<< p1.size() <<endl;
    
    //給最小值優先順序佇列入棧
    p3.push(33);
    p3.push(11);
    p3.push(55);
    p3.push(22);
    
    //列印最小優先順序佇列的對頭元素
    cout<<"對頭元素:"<< p3.top() <<endl;
    cout<<"佇列的大小:"<< p3.size() <<endl;
    
}
複製程式碼

7.set容器

集合, 用來判斷某一個元素是不是在一個組裡面,使用的比較少,底層資料結構為紅黑樹,有序,不重複

  • C++的set容器,其中包含的元素是唯一的,而且是有序的。
  • C++的set容器,是按照順序插入的,不能在指定位置插入。
  • C++的set容器,其結構是紅黑二叉樹,插入資料的效率比vector快

【set元素的插入和刪除】

set提供了insert和erase函式,用來對元素進行插入和刪除操作。

  • 基礎型別資料,如果插入的是重複的元素,則插入失敗,返回值是一個pair型別(pair型別類似於swift語言中的元組的概念,通過其判斷是否插入成功)

  • 複雜型別資料,需要通過仿函式來確定元素的順序,判斷是否是重複元素。

void main()
{
    set<int> set1;
    
    //插入元素
    for (int i = 0; i<5; i++) {
        int tmp = rand();
        set1.insert(tmp);
    }
    
    //重複插入元素(會插入不成功,下一節會分析如果根據返回值判斷是否插入成功)
    set1.insert(100);
    set1.insert(100);
    set1.insert(100);
    set1.insert(100);
    
    for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
        cout << *it <<" ";
    }
    
    
    //刪除集合
    while(!set1.empty())
    {
        //獲取頭部
        set<int>::iterator it = set1.begin();
        
        //列印頭部元素
        cout << *it << endl;
        
        //從頭部刪除元素
        set1.erase(set1.begin());
    }
    
}

複製程式碼

【普通資料型別的排序】

set容器是有序的集合,預設的順序是從小到大的。建立集合的方式:

  • set建立預設的從小到大的int型別的集合

  • set<int,less>建立一個從小到大的int型別的集合

  • set<int,greater>建立一個從大到小的int型別的集合

上面的less和greater就是仿函式,集合會根據這個仿函式的返回值是否為真類進行排序。

//仿函式的原型,下面是C++提供的預設的greater的仿函式(刪除了巨集定義後的)
struct greater
{
    bool operator()(const int &left, const int &right) const
    {
        //如果左值>右值,為真。從大到小的排列
        return left > right;
    }
};
複製程式碼

新增進set集合的元素確實是有序的。

void main()
{
    //預設,從小到大
    set<int> set1;
    //從小到大--預設就是
    set<int, less<int>> set2;
    //從大到小
    set<int, greater<int>> set3;

    //新增元素
    for (int i = 0; i < 5; i++) {
        int tmp = rand();
        set3.insert(tmp);
    }
    
    //遍歷
    for (set<int>::iterator it = set3.begin(); it != set3.end(); it++) {
        cout<< *it << " ";
    }
}
複製程式碼

【自定義物件的排序】

基礎資料型別的set是有序的關鍵原因是greater和less仿函式。

自定義物件的有序是通過自定義仿函式來實現。

仿函式,之所以叫仿函式,是因為它跟函式很像,但並不是一個函式。它的結果如下,只要我們實現了這個仿函式,我們也可以對自定義物件進行排序。

//定義仿函式的結構體
struct FunctionName
{
    //過載了()運算子,實現兩個自定義物件的比較
    bool opeartor() (Type &left, Type &right)
    {
        //左值大於右值,從大到小的順序
        if(left > right) 
            return true;
        else
            return false;
    }
};
複製程式碼

下面,我們自定義一個Student物件,根據年齡進行排序,將物件加入到set集合中,並進行列印。

//定義student物件
class Student {
public:
    Student(const char *name, int age)
    {
        strcpy(this->name, name);
        this->age = age;
    }
    
public:
    char name[64];
    int  age;
};

//提供仿函式,用於自定義物件的set進行排序,要寫一個仿函式,用來排序
struct FuncStudent
{
    //過載了括號操作符,用來比較大小
    bool operator() (const Student &left, const Student &right)
    {
        //如果左邊比右邊小,從小到大按照年齡排序
        if(left.age < right.age)
            return true;
        else
            return false;
    }
    
};

void main()
{
    Student s1("s1",32);
    Student s2("s2",22);
    Student s3("s3",44);
    Student s4("s4",11);
    Student s5("s5",22); 
    
    //建立集合,採用從小到大的排序
    set<Student, FuncStudent> set1;
    
    //插入資料
    set1.insert(s1);
    set1.insert(s2);
    set1.insert(s3);
    set1.insert(s4);
    //插入不進去(年齡有重複的,所以插不進去了),要通過返回值來確保是否插入成功
    set1.insert(s5);    
    
    //遍歷
    for (set<Student>::iterator it = set1.begin(); it != set1.end(); it++) {
        cout << it->age << "\t" << it->name <<endl;
    }
    
}
複製程式碼

【pair型別的返回值】

pair型別,就類似於Swift語言中的“元組”的概念,這個型別包含了多個資料型別,在函式返回的時候,可以同時返回多個值。

它實際上是一個結構體。它包含了兩個屬性,first和second。

template <class _T1, class _T2>
struct pair
{
    typedef _T1 first_type;
    typedef _T2 second_type;

    _T1 first;
    _T2 second;
}
複製程式碼

set集合中的元素是唯一的,重複的元素插入會失敗。

判斷是否插入成功,我們可以通過insert函式的返回值來判斷,它的返回值是一個pair型別。

insert函式的原型: pair<iterator,bool> insert(const value_type& __v)

返回的是pair<iterator, bool>型別,pair的第一個屬性表示當前插入的迭代器的位置,第二個屬性表示插入是否成功的bool值。我們可以通過第二個屬性來判斷元素是否插入成功。

//pair的使用判斷set的insert函式的返回值
void test3()
{
    Student s1("s1",32);
    Student s2("s2",22);
    Student s3("s3",44);
    Student s4("s4",11);
    Student s5("s5",22);
    
    //建立集合,採用從小到大的排序
    set<Student, FuncStudent> set1;

    //插入資料,接收返回值
    pair<set<Student, FuncStudent>::iterator, bool> pair1 = set1.insert(s1);
    if (pair1.second == true) {
        cout << "插入s1成功" <<endl;
    }else{
        cout << "插入s1失敗" <<endl;
    }
複製程式碼

【set查詢元素】

set容器提供了多個函式用來查詢元素

  • iterator find(const key_type& __k) find函式查詢元素為k的迭代器位置

  • iterator lower_bound(const key_type& __k) lower_bound函式查詢小於等於元素k的迭代器位置

  • iterator upper_bound(const key_type& __k) upper_bound函式查詢大於元素k的迭代器位置

  • pair<iterator,iterator> equal_range(const key_type& __k) equal_range函式返回一個pair型別,第一個屬性表示大於等於k的迭代器位置,第二個是大於k的迭代器位置

void test4()
{
    set<int> set1;
    
    for (int i = 0; i < 10; i++) {
        set1.insert(i+1);
    }
    
    //遍歷
    for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
        cout << *it <<endl;
    }
    
    //查詢元素是5的迭代器位置
    set<int>::iterator it0 = set1.find(5);
    cout << "it0:" << *it0 <<endl;
    
    //查詢小於等於5的元素迭代器位置
    set<int>::iterator it1 = set1.lower_bound(5);
    cout << "it1:" << *it1 <<endl;
    
    //查詢大於5的元素迭代器位置
    set<int>::iterator it2 = set1.upper_bound(5);
    cout << "it2:" << *it2 <<endl;
    
    //返回的pair第一個迭代器是>=5,另一個是>5
    pair<set<int>::iterator, set<int>::iterator> mypair = set1.equal_range(5);
    set<int>::iterator it3 = mypair.first;
    set<int>::iterator it4 = mypair.second;
    cout << "it3:" << *it3 <<endl;
    cout << "it4:" << *it4 <<endl; 
}
複製程式碼

8.multiset容器

底層資料結構為紅黑樹,有序,可重複

multiset容器,與set容器相似,但是multiset容器中的元素可以重複。另外,他也是自動排序的,容器內部的值不能隨便修改,因為有順序的。

void test5()
{
    //定義multiset
    multiset<int> set1;
    
    //從鍵盤不停的接收值
    int tmp = 0;
    printf("請輸入multiset集合的值:");
    scanf("%d", &tmp);
    while (tmp != 0) {
        set1.insert(tmp);
        scanf("%d", &tmp);
    }
    
    //迭代器遍歷
    for (multiset<int>::iterator it = set1.begin(); it != set1.end(); it++) {
        cout<< *it <<" ";
    }
    cout <<endl;
   
    //刪除
    while (!set1.empty()) {
        multiset<int>::iterator it = set1.begin();
        cout << *it << " ";
        set1.erase(it);
    }
}
複製程式碼

9.map容器

對映,相當於字典,把一個值對映成另一個值,如果想建立字典的話使用它。底層採用的是樹型結構,多數使用平衡二叉樹實現,查詢某一值是常數時間,遍歷起來效果也不錯,只是每次插入值的時候,會重新構成底層的平衡二叉樹,效率有一定影響。底層資料結構為紅黑樹,有序,不重複

【map元素的插入與刪除】

  • 插入方法一:呼叫insert函式插入pair型別的鍵值對。map的insert函式返回的是pair型別,pair的第二個參數列示是否插入成功。如果插入的元素鍵名相同,則插入失敗。
  • 插入方法二:直接使用[]來對鍵進行復制。 -刪除:跟上面其他的容器一樣,都是直接呼叫erase函式.
int main()
{
    map<int, string> map1;
    
    //insert方法插入
    //--1 通過pair<int, string>(1,”chenhua“) 構造pair元素
    map1.insert(pair<int, string>(1,"chenhua"));
    //--2 通過make_pair構造pair元素
    map1.insert(make_pair(2,"mengna"));
    //--3 通過value_type構造pair元素
    map1.insert(map<int, string>::value_type(3,"chenmeng"));
    
    //[]直接插入
    map1[4] = "menghua";
    
    //重複插入(插入會不成功)
    pair<map<int, string>::iterator, bool> pair1 = map1.insert(make_pair(2, "haha"));
    if (pair1.second) {
        cout << "重複插入成功" << endl;
    }else{
        cout << "重複插入失敗" << endl;
    }
    
    //元素的修改
    //map[1] = "22"的方式,如果不存在鍵則插入,存在鍵則修改
    map1[2] = "haha";
    
    //元素的刪除
    //--刪除值為"haha"的元素
    for (map<int, string>::iterator it = map1.begin(); it != map1.end(); it++) {
        if (it->second.compare("haha") == 0) {
            map1.erase(it);
        }
    }
    
    //遍歷
    for (map<int, string>::iterator it = map1.begin(); it != map1.end(); it++) {
        cout << it->first << "\t" << it->second << endl;
    }
    
    return 0;
}
複製程式碼

【map元素的查詢】

map提供了兩個函式進行key的查詢:find和equal_range。

int main()
{
    //定義map
    map<int ,string> map1;
    map1[1] = "chenhua";
    map1[2] = "mengna";
    
    //查詢key=100的鍵值對
    map<int, string>::iterator it = map1.find(100);
    if (it != map1.end()) {
        cout << "存在key=100的鍵值對";
    }else{
        cout << "不存在" << endl;
    }
    
    
    //查詢key = 1000的位置
    //返回兩個迭代器,第一個表示<=1000的迭代器位置,第二個是>1000的迭代器位置
    pair<map<int, string>::iterator, map<int, string>::iterator> mypair = map1.equal_range(1000);
    if (mypair.first == map1.end()) {
        cout << "大於等於5的位置不存在" << endl;
    }else{
        cout << mypair.first->first << "\t" << mypair.first->second << endl;
    }
    
    if (mypair.second == map1.end()) {
        cout << "大於5的位置不存在" << endl;
    }else{
        cout << mypair.second->first << "\t" << mypair.second->second << endl;
    }
    
    return 0;
}
複製程式碼

10.multimap容器

底層資料結構為紅黑樹,有序,可重複

multimap容器,與map容器的唯一區別是:multimap支援多個鍵值。

由於支援多個鍵值,multimap提供了cout函式來計算同一個key的元素個數。

class Person {
public:
    string name;    //姓名
    int age;        //年齡
    string tel;     //電話
    double sal;     //工資
    
};

void test()
{
    Person p1,p2,p3,p4,p5;
    p1.name = "王1";
    p1.age  = 31;
    
    p2.name = "王2";
    p2.age  = 31;
    
    p3.name = "張3";
    p3.age  = 31;
    
    p4.name = "張4";
    p4.age  = 31;
    
    p5.name = "錢5";
    p5.age  = 31;
    
    multimap<string, Person> map2;
    
    //sale部門
    map2.insert(make_pair("sale", p1));
    map2.insert(make_pair("sale", p2));
    
    //development部門
    map2.insert(make_pair("development", p3));
    map2.insert(make_pair("development", p4));
    
    //Finanncial部門
    map2.insert(make_pair("Finanncial", p5));
    
    //遍歷
    for (multimap<string, Person>::iterator it = map2.begin(); it != map2.end(); it++) {
        
        
        cout << it->first << "\t" << it->second.name << endl;
        
    }
    
    //按部門顯示員工資訊
    int developNum = (int) map2.count("development");
    cout << "development部門人數:" << developNum << endl;
    multimap<string,Person>::iterator it2 = map2.find("development");
    int tag = 0;
    while (it2 != map2.end() && tag < developNum) {
        cout << it2->first << "\t" << it2->second.name <<endl;
        it2 ++;
        tag ++;
    }
    
    //把age=32 修改name= 32
    for (multimap<string, Person>::iterator it = map2.begin(); it != map2.end(); it++) {
        if (it->second.age == 32) {
            it->second.name = "32";
        }
    }
}

int main(int argc, const char * argv[]) {
 
    test();
    
    return 0;
}
複製程式碼

11.hash_set

底層資料結構為hash表,無序,不重複


12.hash_multiset

底層資料結構為hash表,無序,可重複


13.hash_map

底層資料結構為hash表,無序,不重複


14.hash_multimap

底層資料結構為hash表,無序,可重複

二、STL容器的通用性探究

STL的容器主要利用了C++的模板特性來實現。需要注意:

  • 容器快取了節點,節點類要確保支援拷貝(否則出現淺拷貝問題,導致崩潰)
  • 容器中的一般節點類,需要提供拷貝建構函式,並過載等號操作符(用來賦值)
  • 容器在插入元素時,會自動進行元素的拷貝。

針對容器,容器之間也支援拷貝。所以需要注意:

  • 除了queue和stack外,每個容器都提供了可返回迭代器的函式,運用返回的迭代器就可以訪問元素
  • 通常STL不會丟擲異常,要求使用者確保傳入正確的引數
  • 每個容器都提供了一個預設建構函式和一個預設拷貝建構函式

【STL容器的元素拷貝】 如果容器元素如果沒有實現拷貝建構函式,出現淺拷貝後的崩潰問題。

#include <iostream>
#include <string>
#include <vector>
using namespace std;


class Student {
    
public:
    Student(const char *name, int age)
    {
        cout << "建構函式" << endl;
        
        //分配記憶體空間
        m_name = new char[strlen(name) + 1];
        //值拷貝
        strcpy(m_name, name);
        
        m_age = age;
    }
    
    
    ~Student()
    {
        printf("%p 指向的空間 呼叫解構函式\n", m_name);
        if (m_name != NULL) {
            delete []m_name;
            m_age = 0;
        }
    }
    
private:
    char *m_name;
    int   m_age;
    
};


int main()
{
    Student s1("chenhua",24);
    
    vector<Student> v1;
    v1.push_back(s1);
    
    return 0;
}
複製程式碼

上面的程式碼段,執行後的結果如下:

建構函式
0x100302a00 指向的空間 呼叫解構函式
0x100302a00 指向的空間 呼叫解構函式
複製程式碼

執行後,列印出結果後並報錯。報錯原因是同一個記憶體空間被釋放了2次,導致的崩潰。

其根本原因是,v1將s1拷貝到容器,由於Student沒有重寫拷貝建構函式,從而出現了淺拷貝,只拷貝了地址。釋放的時候毫無疑問出現錯誤。 如果我們給Student重寫了拷貝建構函式和過載了等號操作符,則上面的錯誤就不會出現。

//重寫拷貝建構函式
Student(const Student &obj)
{
    //分配記憶體空間
    m_name = new char[strlen(obj.m_name) + 1];
    //值拷貝
    strcpy(m_name, obj.m_name);
    
    m_age = obj.m_age;
}
    
//過載等號運算子
Student & operator=(const Student &obj)
{
    //釋放舊值
    if (m_name != NULL) {
        delete [] m_name;
        m_age = 0;
    }
    
    //分配記憶體空間並賦值
    m_name = new char[strlen(obj.m_name) + 1];
    strcpy(m_name, obj.m_name);
    m_age = obj.m_age;
    
    return *this;
}
複製程式碼

三、STL容器的比較

C++ stl容器詳解

內容參考:www.jianshu.com/p/497843e40…

相關文章