STL(一)vector、set/multiset、list

Vector

动态数据,可以随机访问。末尾添加和删除的效率高。元素的顺序和推入的顺序一致。

基本函数

  • push_back 数组最后添加一个元素。如果是对象会执行对象的拷贝构造函数

  • pop_back 去掉数组最后一个元素

  • at 根据下标得到数据的引用,可以当左值。越界会抛异常

  • [] 可以使用[]操作符,得到结果可以当左值。越界不会抛异常

  • begin 得到数组头的指针iterator

  • end 得到数组的最后一个单元+1的指针 iterator

  • front 数组首元素的引用

  • back 数组尾元素的引用

  • size 数组大小

  • earse 删除参数为iterator

  • insert 指定位置插入元素

  • empty 判断空

遍历

迭代器方式

void printV(vector<int>& v) {
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << "  ";
    }
    cout << endl;
}

删除

vector<int>::iterator it = v.begin();
v.erase(v.begin(), v.end() - 1);

案例

void test() {
    vector<Student> v;
    Student stu1(1, "first");
    Student stu2(2, "second");
    Student stu3(3, "third");
    v.push_back(stu1);
    cout << "------------" << endl;
    v.push_back(stu2);
    cout << "------------" << endl;
    vector<Student>::iterator it = v.begin();
    v.insert(it, stu3);
    cout << "------------" << endl;
    printStud(v);
}

运行结果:


insert.png

Set

vector封装数组,list封装了链表,map和set封装了二叉树等。set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。set插入是按照一定规则排序,默认是由小到大。

  • 为何map和set的插入删除效率比用其他序列容器高?

    对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。

  • 为何每次insert之后,以前保存的iterator不会失效?

    iterator这里就相当于指向元素的指针,元素的内存没有变,指向内存的指针当然也不会失效,当然这里排除元素被删除的情况。相对于vector,每一次删除和插入,指针都可能失效。所以一定要记住,不要使用过期的iterator

      set<int> s;
      s.insert(3);
      set<int>::iterator it1 = s.begin();
      cout << *it1 << endl;
      s.insert(1);
      //虽然插入1后,1在3的前面。但是it1指向的元素的内存。而set内所有的元素以节点
      //方式存储,节点结构合链表差不多,插入和删除元素不需要做内存的移动,只需节点做
      //变换即可
      //牢记这个原则:不要使用过期的iterator。
      cout << *it1 << endl;
      s.insert(2);
    

    这里档次打印的it1结果都是3.

  • 当数据元素增多时,set的插入和搜索速度变化如何?

    set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。

相关函数

  • equal_range()

    返回>=制定元素的第一个元素指针和>制定元素的第一个元素的指针。返回的是pair类型,如果哪个返回失败,就会等于end()的值。

    pair<set<int>::iterator, set<int>::iterator> p1=s.equal_range(4);
    if (p1.first != s.end()) {
      cout <<">=4 is "<< *p1.first << endl;
    }
    if (p1.second != s.end()) {
      cout << ">4 is " << *p1.second << endl;
    }
    
  • insert

    • insert(key_value) 将key_value插入到set中 ,返回值是pair<set<int>::iterator,bool>bool标志着插入是否成功,而iterator代表插入的元素的定位器。

      pair<set<int>::iterator, bool> p2=s.insert(7);
      if (p2.second) {
        cout <<"insert success"<< *p2.first << endl;
      }
      
    • inset(first,second)

      将定位器first到second之间的元素插入到set中,返回值是void

  • lower_bound(key_value)

    是>=值的第一个元素指针

  • upper_bound(key_value)

    upper_bound是>值的第一个元素指针

    cout << *s.lower_bound(5) << " " << *s.upper_bound(5) << endl;
    

测试案例一

#include "iostream"
using namespace std;
#include "set"
#include "vector"
//迭代器遍历
void print(set<int>& s) {
    for (set<int>::iterator it = s.begin(); it != s.end(); it++) {
        cout << *it <<" ";
    }
    cout << endl;
}
int main()
{
    cout << "---------插入----------" << endl;
    set<int> s;
    s.insert(3);
    set<int>::iterator it1 = s.begin();
    cout << *it1 << endl;
    s.insert(1);
    //虽然插入1后,1在3的前面。但是it1指向的元素的内存。而set内所有的元素以节点
    //方式存储,节点结构合链表差不多,插入和删除元素不需要做内存的移动,只需节点做
    //变换即可
    //牢记这个原则:不要使用过期的iterator。
    cout << *it1 << endl;
    s.insert(2);
    s.insert(5);
    s.insert(6);
    print(s);
    cout << "---------equal_range----------" << endl;
    pair<set<int>::iterator, set<int>::iterator> p1=s.equal_range(4);
    if (p1.first != s.end()) {
        cout <<">=4 is "<< *p1.first << endl;
    }
    if (p1.second != s.end()) {
        cout << ">4 is " << *p1.second << endl;
    }
    set<int>::iterator it = s.begin();
    //不可以随机访问,关联型数据结构。红黑二叉树
    //it=it+4;
    cout << "---------erase----------" << endl;
    s.erase(it);
    print(s);
    cout << "---------insert及其返回值----------" << endl;
    pair<set<int>::iterator, bool> p2=s.insert(7);
    if (p2.second) {
        cout <<"insert success"<< *p2.first << endl;
    }
    print(s);
    cout << "---------lower_bound、upper_bound----------" << endl;
    //lower_bound是>=值的最小指针  upper_bound是>值的最小指针
    cout << *s.lower_bound(5) << " " << *s.upper_bound(5) << endl;
    return 0;
}

结果:


set_test1.png

测试案例二

#include "string"
#include "iostream"
#include "set"
using namespace std;
class Student {
private:
    string name;
    int number;
public:
    Student(int number, string name) {
        cout << "构造" << number << endl;
        this->number = number;
        this->name = name;
    }
    Student(const Student& student) {
        this->number = student.getNumber();
        this->name = student.getName();
    }

    void print()const {
        cout << this->number << " "<<this->name << endl;
    }

    string getName()const {
        return this->name;
    }

    int getNumber()const  {
        return this->number;
    }
};
//函数对象
struct StuCmp{
    bool operator()(const Student &first,const Student& second) {
        return first.getNumber() > second.getNumber();
    }
};

//遍历
void print(set<Student, StuCmp>& s) {
    for (set<Student, StuCmp>::iterator it = s.begin(); it != s.end(); it++) {
        it->print();
    }
}
int main()
{
    set<Student, StuCmp> set;
    Student stu1(3, "third");
    Student stu2(1, "first");
    Student stu3(2, "second");
    set.insert(stu1);
    set.insert(stu2);
    set.insert(stu3);
    print(set);
    return 0;
}

结果:


set_test2.png

multiset

set集合中一个值只能出现一次,而multiset集合中一个值可以出现多次。

  • set::insert(key)的返回值是一个pair<iterator, bool>,其中pair中的bool成员表明了key被插入之前,set中是否已存在相同的key。根据我在VS2010上的实验结果,如果set中已经存在相同key的元素,那么插入操作是会失败的,新的元素不会被插进去。而multiset::insert的返回值只是一个iterator,插入操作总是会成功的。
  • multiset::count(key)的返回值可能大于1。
  • multiset::size()的返回值是多重集合的势(cardinality),即multiset中元素的个数,而不是值的个数。比如,{1, 1, 2}的size是3,而不是2。
  • multiset::erase(key)会将对应的key全部删掉,所以对{1, 1, 2}调用erase(1)之后,它就变成了{2}。
  • 只要key存在于集合中,set::equal_range(key)的返回值pair<iterator1, iterator2>总是会有++iterator1 == iterator2。但是对multiset来说就不一定了。
  • 如果使用自定义类型,则需要在构造时候传入函数对象或者在自定义类型中重载<操作符。注意:函数要加const限定符。

什么时候需要用multiset?当然是需要用set,但是又允许重复key存在的时候了。什么时候用set?我的答案是:需要随时往容器中插入元素,随时对元素进行快速查找,又需要按某种顺序对元素进行遍历的时候

示例

int main() {
    multiset<Student, StuCmp> s;
    Student stu1(3, "third");
    Student stu2(1, "first");
    Student stu3(2, "second");
    Student stu4(2, "second_2");
    s.insert(stu1);
    s.insert(stu2);
    multiset<Student,StuCmp>::iterator res= s.insert(stu3);
    cout << "--------插入结果---------" << endl;
    res->print();
    res= s.insert(stu4);
    cout << "--------插入结果---------" << endl;
    res->print();
    cout << "--------遍历---------" << endl;
    print(s);
    Student stu5(2, "second_3");
    cout << "--------count方法---------" << endl;
    cout << "count(5)=" << s.count(stu5) << endl;;
    return 0;
}

这里的Student和StuCmp沿用Set中的定义。编译后发现出错:


multi_set_error.png

发现是StuCmp的问题,函数的const修饰符漏了。修改后

struct StuCmp{
    bool operator()(const Student &first,const Student& second) const{
        return first.getNumber() > second.getNumber();
    }
};

运行结果:


multi_set_test3.png

list

list是一个线性双向链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。由于其结构的原因,list 随机检索的性能非常的不好,因为它不像vector 那样直接找到元素的地址,而是要从头一个一个的顺序查找,这样目标元素越靠后,它的检索时间就越长。检索时间与目标元素的位置成正比。虽然随机检索的速度不够快,但是它可以迅速地在任何节点进行插入和删除操作。因为list 的每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响,不像vector 会对操作点之后的所有元素的存储地址都有所影响,这一点是vector 不可比拟的。

  • 不使用连续的内存空间这样可以随意地进行动态操作
  • 可以在内部任何位置快速地插入或删除,当然也可以在两端进行push 和pop
  • 不能进行内部的随机访问,即不支持[ ] 操作符和vector.at()
  • 相对于verctor 占用更多的内存

示例

void print(list<Student> l) {
    for (list<Student>::iterator it = l.begin(); it != l.end(); it++) {
        it->print();
    }
}
struct PrintStu{
    bool operator()(const Student& stu)const  {
        stu.print();
        return true;
    }
};
int main()
{
    list<Student> l;
    Student stu1(1, "first");
    Student stu2(2, "second");
    Student stu3(3, "third");
    Student stu4(4, "fourth");
    l.push_back(stu1);
    l.push_front(stu2);
    l.push_back(stu3);
    l.push_front(stu4);
    print(l);
    cout << "---pop_back、pop_front-------" << endl;
    Student s=l.back();
    s.print();
    l.pop_back();
    s = l.front();
    s.print();
    l.pop_front();
    cout << "----for_each------" << endl;
    for_each(l.begin(), l.end(), PrintStu());
    return 0;
}

结果:

list.png

这里使用了for_each函数算法。需要引入#include<algorithm>头文件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容