C++ multiset,STL multiset詳解

2020-07-16 10:04:28
multiset 是關聯容器的一種,是排序好的集合(元素已經進行了排序),並且允許有相同的元素。

不能直接修改 multiset 容器中元素的值。因為元素被修改後,容器並不會自動重新調整順序,於是容器的有序性就會被破壞,再在其上進行查詢等操作就會得到錯誤的結果。因此,如果要修改 multiset 容器中某個元素的值,正確的做法是先刪除該元素,再插入新元素。

使用 multiset 必須包含標頭檔案 <set>。multiset 類別範本的定義如下:

template <class Key, class Pred = less<Key>, class B = allocator<Key> > class multiset {
    ...
};

該模板有三個型別引數:Key、Pred 和 B。型別引數可以有預設值,預設值就是某種型別。例如,Pred 型別引數的預設值就是 less<Key> 型別,B 的預設值就是 allocator<Key> 型別。第三個型別引數極少用到,一般都用預設值,因此這裡不做介紹。

第一個型別引數說明 multiset 容器中的每個元素都是 Key 型別的。第二個型別引數 Pred 用於指明容器中元素的排序規則,在被範例化後,Pred 可以是函數物件類,也可以是函數指標型別。

multiset 內部在排序時定義了一個變數Pred op,根據表示式op(x, y)來比較兩個元素 x、y 的大小。該表示式的值為 true,則說明 x 比 y 小。Pred 的預設值是 less<Key>,less 是 STL 中的函數物件類別範本,其定義如下:
template <class_Tp>
struct less
{
    bool operator() (const _Tp &__x, const _Tp &__y) const
    { return __x < __y; }
};
這說明,在預設情況下,multiset 容器中的元素是用<運算子比較大小的。例如,假設 A 是一個類的名字,可以定義一個如下的容器物件:

multiset <A> s;

由於 multiset 的型別引數可以使用預設值,因此上面的語句等價於:

multiset < int, less<A>, allocator<A> > s;

模板類 multiset < A, less<A>, allocator<A> > 的 insert 成員函數可以用來插入一個元素。 插入過程中需要進行元素之間的比較,可以認為 insert 成員函數中定義了一個變數 less <A> op,用 op(x, y) 來比較元素 x、y 的大小。歸根到底,還是用<運算子比較 x、y 的大小。 因此,<運算子必須經過適當過載,才可以向 multiset <A>容器中插人元素。

下面的程式 會編譯出錯:
#include <set>
using namespace std;
class A{};
int main(){
    multiset <A> a;
    a.insert( A() );  //編譯出錯,因為不能用“<”運算子比較兩個A物件
}
multiset 常用的成員函數如表 1 所示。有的成員函數有不止一個版本,這裡不一一 列出。

表1:multiset 的成員函數
成員函數或成員函數模板 作  用
iterator find (const T & val); 在容器中查詢值為 val 的元素,返回其疊代器。如果找不到,返 回 end()
iterator insert( const T & val); 將 val 插入容器中並返回其疊代器
void insert(iterator first, iterator last); 將區間 [first, last) 中的元素插人容器
int count( const T & val); 統計有多少個元素的值和 val 相等
iterator lower_bound( const T & val); 查詢一個最大的位置 it,使得 [begin(), it) 中所有的元素者比 val 小
iterator upper_bound( const T & val); 查詢一個最小的位置 it,使得 [it, end()) 中所有的元素都比 val 大
pair <iterator, iterator > equal_range (const T & val); 同時求得 lower_bound 和 upper_bound
iterator erase(iterator it); 刪除 it 指向的元素,返回其後面的元素的疊代器(Visual Studio 2010 中如此,但是在 C++ 標準和 Dev C++ 中,返回值不是這樣)
iterator erase(iterator first, iterator last); 刪除區間 [first, last),返回 last(Visual Studio 2010 中如此,但是在 C++ 標準和 Dev C++ 中,返回值不是這樣)

multiset 及 set 中的 find 和 count 並不是用==運算子比較元素是否和待查詢的值相等的。它們進行比較的原則是:如果x比y小y比x小同時為假,就認為 x 和 y 相等。

下面通過一個例子說明 multiset 的用法。
#include <iostream>
#include <set>  //使用multiset須包含此標頭檔案
using namespace std;
template <class T>
void Print(T first, T last)
{
    for (; first != last; ++first)
        cout << *first << " ";
    cout << endl;
}
class A
{
private:
    int n;
public:
    A(int n_) { n = n_; }
    friend bool operator < (const A & a1, const A & a2)
    { return a1.n < a2.n; }
    friend ostream & operator << (ostream & o, const A & a2)
    { o << a2.n; return o; }
    friend class MyLess;
};
class MyLess
{
public:
    bool operator() (const A & a1, const A & a2)  //按個位數比較大小
    { return (a1.n % 10) < (a2.n % 10); }
};
typedef multiset <A> MSET1;  //MSET1 用“<”運算子比較大小
typedef multiset <A, MyLess> MSET2;  //MSET2 用 MyLess::operator() 比較大小
int main()
{
    const int SIZE = 6;
    A a[SIZE] = { 4, 22, 19, 8, 33, 40 };
    MSET1 m1;
    m1.insert(a, a + SIZE);
    m1.insert(22);
    cout << "1)" << m1.count(22) << endl;  //輸出 1)2
    cout << "2)"; Print(m1.begin(), m1.end());  //輸出 2)4 8 19 22 22 33 40
    MSET1::iterator pp = m1.find(19);
    if (pp != m1.end())  //條件為真說明找到
        cout << "found" << endl;  //本行會被執行,輸出 found
    cout << "3)"; cout << *m1.lower_bound(22)
        << "," << *m1.upper_bound(22) << endl; //輸出 3)22,33
    pp = m1.erase(m1.lower_bound(22), m1.upper_bound(22));
    //pp指向被刪元素的下一個元素
    cout << "4)"; Print(m1.begin(), m1.end());  //輸出 4)4 8 19 33 40
    cout << "5)"; cout << *pp << endl;  //輸出 5)33
    MSET2 m2;  //m2中的元素按n的個位數從小到大排序
    m2.insert(a, a + SIZE);
    cout << "6)"; Print(m2.begin(), m2.end());  //輸出 6)40 22 33 4 8 19
    return 0;
}
第 30 行,MSET2 類的排序規則和 MSET1 不同。MSET2 用 MyLess 定義排序規則,即 n 的個位數小的元素排在前面。

第 43、44 行,lower_bound 返回的疊代器指向第一個 22,upper_bound 返回的疊代器指向 33。

第 45 行,刪除所有值為 22 的元素。erase 成員函數刪除一個元素後,返回下一個元素的疊代器應該是很合理的,但是 C++ 標準委員會認為,返回下一個元素的疊代器也是需要時間開銷的,如果程式設計師不想要這個返回值,那麼這個開銷就是浪費的——因此在遵循 C++ 標準的 Dev C++ 中,本行無法編譯通過。但是微軟公司認為應該對這一點做出改進,因此 Visual Studio 2010 將 erase 成員函數處理成返回被刪元素下一個元素的疊代器。

不論在哪種編譯器中,用 erase 成員函數刪除疊代器 i 指向的元素後,迭代器 i 即告失效, 此時不能指望 ++i 後 i 會指向被刪除元素的下一個元素;相反,++i 可能立即導致出錯。如果想要得到被刪除元素後面那個元素的疊代器,可以在刪除前獲取其疊代器並儲存起來(這同樣適用於 set、map、multimap 的 erase 成員函數)。事實上,如果得到了某關聯容器的疊代器,則該疊代器並不會因為容器中元素的插入以及其他元素的刪除而失效。只要該疊代器指向的元素沒有被刪除,就可以一直使用它。