STL 集上的引用运算符 []

The reference operator [] on STL set

本文关键字:运算符 引用 STL      更新时间:2023-10-16

我刚刚熟悉 STL,我不太明白为什么operator []会给出错误。

        int main(){
          set< int > s;
          for(int i=0; i<=1000; i++) s.insert((i*1777)%123);
          for(int i=0; i<s.size(); i++) cout<<s[i]<<endl;
        }

然后我尝试了这个并收到另一条错误消息

        int main(){
          set< int > s;
          for(int i=0; i<=1000; i++) s.insert((i*1777)%123);
          for(int i=0; i<s.size(); i++) cout<<*(s.begin() + i)<<endl;
        }

我理解为什么它没有像 push_backpop_back 这样的成员,但我不明白为什么这两种引用方法不起作用(但它们适用于vectorstring(。我知道这些运算符在库中没有过载,但为什么呢?

经过一些网络搜索,我确实弄清楚了如何引用它

        int main(){
          set< int > s;
          for(int i=0; i<=1000; i++) s.insert((i*1777)%123);
          for(set< int >::iterator i=s.begin(); i!=s.end(); i++) cout<<*i<<endl;
        }

该标准没有为集合或其迭代器指定这些运算符,因为这些不是访问集合的有效方法。 集合具有双向迭代器。 这意味着,为了移动到迭代序列中的第 n 个元素,您需要迭代其间的每个元素。 因此,例如,如果要为集合的迭代器实现 operator+,在内部,它将是这样的:

iterator operator+(iterator it, size_t n)
{
    for (int i=0; i<n; ++i)
        ++it;
    return it;
}

因此,换句话说,这将是一个 O(n( 操作。 如果你像在 for 循环中那样遍历集合,它就会变成一个 O(n^2( for 循环。 如果实现了operator[],同样的事情也适用。 正因为如此,没有一个考虑效率的人会想要使用这些运算符,因此它们没有实现。

嗯,这是因为std::set没有提供下标operator[]由于集合的性质而完全可以理解的内容。集合[4]应该是什么意思?从数学上讲,这是不正确的。在数学中,set1={1,2,3,4} 和 set2={4,3,2,1} 是相等的,那么如果这些集合中每两个 set1[n] 和 set2[n] 都不同(在std::set的情况下,元素被排序,所以它会是一样的(?因此std::set没有下标operator[]但是您仍然可以循环访问此容器。

int myints1[]= {10,20,30,40,50};
int myints2[]= {50,40,30,20,10}; 
std::set<int> s1 (myints1,myints1+5);
std::set<int> s2(myints2,myints2+5); // Internally, the elements in a set are 
                                     // always sorted following a specific strict
                                     // weak ordering criterion indicated by its
                                     // internal comparison object, so this set
                                     // will be the same as s2
if(s1==s2){
    printf("sets: true");
}else printf("sets: false");
std::set<int>::iterator it2=s2.begin();
for(std::set<int>::iterator it1=s1.begin();it1!=s1.end();it1++){
            printf("ns1: %d  s2: %d",*it1,*it2);
    it2++;
}

输出:

套:真

S1:10

S2:10

S1:20

S2:20

S1:30

S2:30

S1:40

S2:40

S1:50

S2:50

STL 集不会重载下标运算符 [] 。您不能像使用其他容器(如 vector (那样直接使用下标运算符访问 STL 集元素。你可以在这里找到STL集的完整参考:STL集

@BenjaminLindley正确地指出,T& operator[](std::size_t)对于没有随机访问迭代器的容器没有意义,因为所有随机访问循环的复杂性O(N^2)(元素上的外部循环是线性的,迭代器上的std::advance是线性的(。出于这个原因,在序列容器中,只有std::arraystd::vectorstd::deque提供operator[],但std::list(双向迭代器(和std::forward_list(前向迭代器(不提供。

有序关联容器(std::setstd::map和它们的多表亲(只提供双向迭代器,无序关联容器(std::unordered_setstd::unorderd_map和它们的多表亲(至少具有前向迭代器。他们也没有operator[](std::size_t)作为成员。因此,你需要写std::advance(my_set.begin(), n)而不是my_set[n],这使得这种调用的O(N)复杂性令人痛苦。

补充说明:类似映射的容器包含键值对,这些容器的关联性质通过另一个operator[]表示,但不通过偏移量索引,而是使用"关联"键,并且它们具有签名Value& operator[](Key const&)(以及自 C++11 以来的右值引用重载(。这些运算符O(log N) std::map的复杂性,以及摊销O(1) std::unordered_map复杂性。例如,这将使这些容器的所有键的循环分别O(N log N)O(N)复杂性。

关联operator[]版本还具有插入语义:像 my_map[my_key] = my_value; 这样的调用将尝试将my_key, my_value对插入到映射中,如果这样的元素已经存在,则返回迭代器。请注意,这些关联元素访问也没有const重载:为此使用 find() 成员函数。

对于std::set重载operator[](Key const&)没有任何意义,因为它只会表达键与自身关联的事实,并且插入语义已经通过 insert() 成员函数更直接地表达。

如果 BST 的每个节点都保持其后代的计数,则 BST 的第 i 个元素可以在 O(log(n(( 时间内找到,整个树的大小可以在 O(1( 时间内返回。 每次插入和删除的开销将是 O(log(n((,这已经是 O(log(n(( 时间操作。