【问题标题】:The reference operator [] on STL setSTL 集上的引用运算符 []
【发布时间】:2013-04-12 19:07:26
【问题描述】:

我刚刚熟悉 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;
        }

【问题讨论】:

  • 据我所知,STL set 没有operator[]。你确定第一个代码示例可以编译吗?
  • @templatetypedef 它没有编译

标签: c++ visual-c++ stl reference set


【解决方案1】:

该标准没有为集合或其迭代器指定这些运算符,因为这些不是访问集合的有效方法。集合具有双向迭代器。这意味着为了移动到迭代序列中的第 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[],同样的情况也适用。正因为如此,考虑到效率的人都不会想要使用这些运算符,因此它们没有被实现。

【讨论】:

  • 谢谢。这比我得到的更有意义。大多数人只是告诉我我已经知道的。
  • 集合没有“第 n 个元素”。它们是无序的。
  • @Daniel:你熟悉 C++ 吗?这就是我们在这里谈论的语言。特别是std::set,来自其标准库,它肯定是有序的。
  • @Daniel:性能是足够好的理由,同样 std::list 没有随机访问迭代器,出于同样的原因,即使顺序对列表非常有意义。带有包含 1,2,3 的默认比较器的 std::set 将具有第 2 个元素 2。当您迭代 set 时,您可以预期每个下一个元素都将大于前一个。
  • @Daniel:他们本可以,是的。但他们没有。我的回答中概述了他们没有这样做的原因。 std::set 粗略地等同于数学集,是的。但实际上,它被指定的不止于此。特别是,它被指定为有序。标准库的作者没有必须将其指定为有序,但事实上他们做到了。它可以实现为一个哈希集,就像std::unordered_set 一样,它将满足您能够以“某种顺序”访问的要求。但事实并非如此。
【解决方案2】:

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

【讨论】:

  • 谢谢,但这并不能回答我的问题。我试图理解为什么这些运算符没有重载。
【解决方案3】:

@BenjaminLindley 正确地指出 T&amp; 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&amp; operator[](Key const&amp;)(以及自 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&amp;) 没有任何意义,因为它只会表达键与自身相关联的事实,而插入语义已经通过insert() 成员函数更直接地表达了。

【讨论】:

    【解决方案4】:

    嗯,这是因为std::set 没有提供下标operator[],这是完全可以理解的,因为集合的性质。 set[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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-02-20
        • 2017-02-12
        • 1970-01-01
        • 2020-05-06
        • 1970-01-01
        • 2011-12-07
        • 2012-03-08
        相关资源
        最近更新 更多