C++ STL六大组件-1-Container(容器)

C++ STL六大组件-简析

----------------------------------------------------------------------------------------------------------------------------------------------

C++ STL六大组件-1-Container(容器) 

  • 容器定义
  • 常见容器
  • 容器分类
    • 顺序容器
      • vector
      • deque
      • list
    • 关联容器
      • map
      • set
    • 容器适配器
      • queue
      • stack                              

C++ STL六大组件-2-Adapter(适配器) 

C++ STL六大组件-3-Algorithm(算法) 

C++ STL六大组件-4-Iterator(迭代器) 

C++ STL六大组件-5-Function object(函数对象) 

C++ STL六大组件-6-Allocator(分配器)

-----------------------------------------------------------------分割线---------------------------------------------------------------------------------

容器定义

    在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。

    容器就是保存其它对象的对象,当然这是一个朴素的理解,这种“对象”还包含了一系列处理“其它对象”的方法。

 

常见容器

容器是STL中很重要的一种数据结构。常见的容器包括

  • vector容器
  • deque双端数组
  • stack栈模型
  • queue队列模型
  • list链表模型
  • priotriy_queue优先级队列
  • set与multiset容器
  • map与multimap容器

容器分类

常用的容器类型分为:

  1. 顺序容器 (vector/deque/list)是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。顺序容器包括:vector(向量)、list(列表)、deque(队列)。
  2. 关联容器  (map/set)  关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。元素是有序的集合,默认在插入的时候按升序排列。关联容器包括:map(集合)、set(映射)、multimap(多重集合)、multiset(多重映射)。
  3. 容器适配器  (queue/stack)  本质上,适配器是使一种不同的行为类似于另一事物的行为的一种机制容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现,即可以把适配器看作“它保存一个容器,这个容器再保存所有元素”。STL 中包含三种适配器:栈stack 、队列queue 和优先级队列priority_queue 。

顺序容器

1. vector

vector是一种动态数组,在内存中具有连续的存储空间,支持快速随机访问。由于具有连续的存储空间,所以在插入和删除操作方面,效率比较慢。vector有多个构造函数,默认的构造函数是构造一个初始长度为0的内存空间,且分配的内存空间是以2的倍数动态增长的。在push_back的过程中,若发现分配的内存空间不足,则重新分配一段连续的内存空间,其大小是现在连续空间的2倍,再将原先空间中的元素复制到新的空间中,性能消耗比较大。

vector的另一个常见的问题就是clear操作。clear函数只是把vector的size清为零,但vector中的元素在内存中并没有消除,所以在使用vector的过程中会发现内存消耗会越来越多,导致内存泄露,现在经常用的方法是swap函数来进行解决: 

vector V;
V.push_back(1); 
V.push_back(2);
V.push_back(1); 
V.push_back(2);
vector().swap(V); 
//或者 V.swap(vector());

vector 基本用法:

  • front()返回头部元素的引用,可以当左值
  • back()返回尾部元素的引用,可以当左值
  • push_back()添加元素,只能尾部添加
  • pop_back()移除元素,只能在尾部移除
  • erase(iterator) 是根据位置进行删除,如果想要删除某个元素,需要找到当前元素的迭代器位置,再进行删除
  • insert() 结合迭代器位置插入指定的元素
   vec1.push_back(100);            //添加元素

    int size = vec1.size();         //元素个数

    bool isEmpty = vec1.empty();    //判断是否为空

    cout<=、<=...

    vector::iterator iter = vec1.begin();    //获取迭代器首地址

    vector::const_iterator c_iter = vec1.begin();   //获取const类型迭代器

    vec1.clear();                 //清空元素

vector有4种方式初始化,有直接初始化,也要通过拷贝构造函数初始化。

        vector vec1;    //默认初始化,vec1为空


         vector vec2(vec1);  //使用vec1初始化vec2


         vector vec3(vec1.begin(),vec1.end());//使用vec1初始化vec2


         vector vec4(10);    //10个值为0的元素


         vector vec5(10,4);  //10个值为4的元素


         vector vec6(10,"null");    //10个值为null的元素


         vector vec7(10,"hello");  //10个值为hello的元素
int main(int argc, const char * argv[]) {

    //直接构造函数初始化
    vector v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    
    //通过拷贝构造函数初始化
    vector v2 = v1;
    
    //使用部分元素来构造
    vector v3(v1.begin(), v1.begin() + 1);
    vector v4(v1.begin(), v1.end());
    
    //存放三个元素,每个元素都是9
    vector v5(3,9);
    
    return 0;
}

vector的遍历有多种方式,可以根据[]或者迭代器遍历。

需要注意的是:

  • []方式,如果越界或出现其他错误,不会抛出异常,可能会崩溃,可能数据随机出现
  • at方式,如果越界或出现其他错误,会抛出异常,需要捕获异常并处理
  • 迭代器提供了逆向遍历,可以通过迭代器来实现逆向遍历,当然上面两种方式也可以
int main(int argc, const char * argv[]) {
    
    //创建vector
    vector 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::iterator it = v1.begin(); it != v1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
    
    //遍历-迭代器逆向遍历
    for (vector::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;
}

2. deque

deque和vector类似,支持快速随机访问。二者最大的区别在于,vector只能在末端插入数据,而deque支持双端插入数据。

deque的内存空间分布是小片的连续,小片间用链表相连,实际上内部有一个map的指针。deque空间的重新分配要比vector快,重新分配空间后,原有的元素是不需要拷贝的。

与vector不同的是,deque还支持从开始端插入数据:push_front()。其余类似vector操作方法的使用。

  • push_back 从尾部插入元素
  • push_front 从头部插入元素
  • pop_back 从尾部删除元素
  • pop_front 从头部删除元素

插播一个知识点

distance函数可以求出当前的迭代器指针it距离头部的位置,也就是容器的指针

用法: distance(v1.begin(), it)

3. list

list是一个双向链表,因此它的内存空间是可以不连续的,通过指针来进行数据的访问,这使list的随机存储变得非常低效,因此list没有提供[]操作符的重载。但list可以很好地支持任意地方的插入和删除,只需移动相应的指针即可。

 

list的定义和初始化:

  list lst1;          //创建空list


    list lst2(3);       //创建含有三个元素的list


    list lst3(3,2); //创建含有三个元素的值为2的list


    list lst4(lst2);    //使用lst2初始化lst4


    list lst5(lst2.begin(),lst2.end());  //同lst4

list的常见操作:

  lst1.assign(lst2.begin(),lst2.end());  //分配值


    lst1.push_back(10);                    //添加值


    lst1.pop_back();                   //删除末尾值


    lst1.begin();                      //返回首值的迭代器


    lst1.end();                            //返回尾值的迭代器


    lst1.clear();                      //清空值


    bool isEmpty1 = lst1.empty();          //判断为空


    lst1.erase(lst1.begin(),lst1.end());                        //删除元素


    lst1.front();                      //返回第一个元素的引用


    lst1.back();                       //返回最后一个元素的引用


    lst1.insert(lst1.begin(),3,2);         //从指定位置插入3个值为2的元素


    lst1.rbegin();                         //返回第一个元素的前向指针


    lst1.remove(2);                        //相同的元素全部删除


    lst1.reverse();                        //反转


    lst1.size();                       //含有元素个数


    lst1.sort();                       //排序


    lst1.unique();                         //删除相邻重复元素

选择顺序容器的原则

实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面的原则:
1) 如果你需要高效的随机存取,而不在乎插入和删除的效率,使用vector
2) 如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3) 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque

顺序容器使用方法公共点:

添加元素

函数名 意义
c.push_back(t)

在容器c的尾部添加值为t的元素。返回void 类型

c.push_front(t)

在容器c的前端添加值为t的元素。返回void 类型

只适用于list和deque容器类型。

c.insert(p,t)

在迭代器p所指向的元素前面插入值为t的新元素。返回指向新添加元素的迭代器。

c.insert(p,n,t)

在迭代器p所指向的元素前面插入n个值为t的新元素。返回void 类型

c.insert(p,b,e)

在迭代器p所指向的元素前面插入由迭代器b和e标记的范围内的元素。返回 void 类型

查看容器大小:

函数名 意义
c.size()

返回容器c中元素个数。返回类型为 c::size_type

c.max_size()

返回容器c可容纳的最多元素个数,返回类型为c::size_type

c.empty()

返回标记容器大小是否为0的布尔值

c.resize(n)

调整容器c的长度大小,使其能容纳n个元素,如果n

c.resize(n,t)

调整容器c的长度大小,使其能容纳n个元素。所有新添加的元素值都为t

访问元素:

函数名 意义
c.back()

返回容器 c 的最后一个元素的引用。如果 c 为空,则该操作未定

c.front() 返回容器 c 的第一个元素的引用。如果 c 为空,则该操作未定义
c[n]

返回下标为 n 的元素的引用。如果 n <0 或 n >= c.size(),则该操作未定义

只适用于 vector 和 deque 容器

c.at(n)

返回下标为 n 的元素的引用。如果下标越界,则该操作未定义

只适用于 vector 和 deque 容器

删除元素:

函数名 意义
c.erase(p)

删除迭代器p所指向的元素。返回一个迭代器,它指向被删除元素后面的元素。如果p指向容器内的最后一个元素,则返回的迭代器指向容器超出末端的下一位置。如果p本身就是指向超出末端的下一位置的迭代器,则该函数未定义

c.erase(b,e)

删除迭代器b和e所标记的范围内所有的元素。返回一个迭代器,它指向被删除元素段后面的元素。如果e本身就是指向超出末端的下一位置的迭代器,则返回的迭代器也指向容器的超出末端的下一位置

c.clear() 删除容器c内的所有元素。返回void
c.pop_back()

删除容器c的最后一个元素。返回void。如果c为空容器,则该函数未定义

c.pop_front()

删除容器c的第一个元素。返回void。如果c为空容器,则该函数未定义

只适用于 list 或 deque 容器

赋值与swap

函数名

意义

c1 = c2

删除容器c1的所有元素,然后将c2的元素复制给c1。c1和c2的类型(包括容器类型和元素类型)必须相同

c1.swap(c2)

交换内容:调用完该函数后,c1中存放的是 c2 原来的元素,c2中存放的则是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比将c2复制到c1的操作快

c.assign(b,e)

重新设置c的元素:将迭代器b和e标记的范围内所有的元素复制到c中。b和e必须不是指向c中元素的迭代器

c.assign(n,t) 将容器c重新设置为存储n个值为t的元素

关联容器

关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。


关联容器支持高效的关键字查找与访问。两个主要的关联容器类型是map与set。

 在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering):可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。
       对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。

1. map

   C++中map容器提供一个键值对(key/value)容器,map与multimap差别仅仅在于multiple允许一个键对应多个值。对于迭代器来说,可以修改实值,而不能修改key。Map会根据key自动排序。

  map 是键-值对的集合。map 类型通常可理解为关联数组:可使用键作为下标来获取一个值,正如内置数组类型一样。而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。

map内部自建一棵红黑树(一种自平衡二叉树),这棵树具有数据自动排序的功能,所以在map内部所有的数据都是有序的,以二叉树的形式进行组织。

map的定义与初始化:

函数名 意义

mapm

创建一个名为m的空map对象,其键和值的类型分别为k和v

map

m(m2)

创建m2的副本m,m与m2必须有相同的键类型和值类型

map

m(b, e)

创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本。元素的类型必须能转换为pair

map添加元素:

函数名 意义
m.insert(e)

e是一个用在m上的value_type 类型的值。如果键(e.first不在m中,则插入一个值为e.second 的新元素;如果该键在m中已存在,则保持m不变。该函数返回一个pair类型对象,包含指向键为e.first的元素的map迭代器,以及一个 bool 类型的对象,表示是否插入了该元素

m.insert

(beg,end)

beg和end是标记元素范围的迭代器,其中的元素必须为m.value_type 类型的键-值对。对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。返回 void 类型

m.insert

(iter,e)

e是一个用在m上的 value_type 类型的值。如果键(e.first)不在m中,则创建新元素,并以迭代器iter为起点搜索新元素存储的位置。返回一个迭代器,指向m中具有给定键的元素

map删除元素:

函数名 意义
m.erase(k)

删除m中键为k的元素。返回size_type类型的值,表示删除的元素个数

m.erase(p)

从m中删除迭代器p所指向的元素。p必须指向m中确实存在的元素,而且不能等于m.end()。返回void

m.erase(b,

e)

从m中删除一段范围内的元素,该范围由迭代器对b和e标记。b和e必须标记m中的一段有效范围:即b和e都必须指向m中的元素或最后一个元素的下一个位置。而且,b和e要么相等(此时删除的范围为空),要么b所指向的元素必须出在e所

指向的元素之前。返回 void 类型

map遍历元素:

函数名 意义
m.count(k)

返回 m 中 k 的出现次数

m.find(k) 如果m容器中存在按k索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器。

 

map元素查找:

map提供了两个函数进行key的查找:find和equal_range。

map和multimap的对比:

唯一区别是multimap支持多个键值。由于支持多个键值,multimap提供了cout函数来计算同一个key的元素个数。

 

2. set

set也是一种关联性容器,它同map一样,底层使用红黑树实现,插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝,所以效率比较高

 set的含义是集合,它是一个有序的容器,里面的元素都是排序好的,支持插入,删除,查找等操作,就像一个集合一样。所有的操作的都是严格在logn时间之内完成,效率非常高。

set中的元素都是唯一的,而且默认情况下会对元素进行升序排列。set和multiset的区别是:set插入的元素不能相同,但是multiset可以相同。Set默认自动排序。使用方法类似list。

所以在set中,不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,再插入新元素。不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。

 

set的定义和使用:

set容器是有序的集合,默认的顺序是从小到大的。

创建集合的方式:

  • set创建默认的从小到大的int类型的集合
  • setless>创建一个从小打到大的int类型的集合
  • setgreater>创建一个从大到小的int类型的集合

上面的less和greater就是仿函数,集合会根据这个仿函数的返回值是否为真类进行排序。

set 容器的每个键都只能对应一个元素。以一段范围的元素初始化set对象,或在set对象中插入一组元素时,对于每个键,事实上都只添加了一个元素。

vector ivec;

for (vector::size_type i = 0; i != 10; ++i) {

ivec.push_back(i);

ivec.push_back(i);

}

set iset(ivec.begin(), ivec.end());

cout << ivec.size() << endl; //20个

cout << iset.size() << endl; // 10个

set添加和删除元素:set提供了inserterase函数,用来对元素进行插入和删除操作。

set set1;

set1.insert("the"); //第一种方法:直接添加

set iset2;

iset2.insert(ivec.begin(), ivec.end());//第二中方法:通过指针迭代器

//删除集合
while(!set1.empty())
{
    //获取头部
    set::iterator it = set1.begin();
        
    //打印头部元素
    cout << *it << endl;
        
    //从头部删除元素
    set1.erase(set1.begin());
}
 

set获取元素:不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。

 set 容器不提供下标操作符。为了通过键从 set 中获取元素,可使用 find运算。

如果只需简单地判断某个元素是否存在,同样可以使用 count 运算,返回 set 中该键对应的元素个数。
当然,对于 set 容器,count 的返回值只能是1(该元素存在)或 0(该元素不存在)。

set iset;

for(int i = 0; i<10; i++)iset.insert(i);

iset.find(1) // 返回指向元素内容为1的指针

iset.find(11) // 返回指针iset.end()

iset.count(1) // 存在,返回1

iset.count(11) // 不存在,返回0

迭代器的关联容器操作:

函数名 意义
m.lower_bound(k)

返回一个迭代器,指向键不小于 k 的第一个元素

m.upper_bound(k) 返回一个迭代器,指向键大于 k 的第一个元素
m.equal_range(k)

返回一个迭代器的 pair 对象。它的 first 成员等价于 m.lower_bound(k)。而 second 成员则等价于 m.upper_bound(k)

 

 

容器适配器

 适配器(Adaptors)是标准库中的一个通用概念,容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。

一个容器适配器(Container adaptors)接受一种已有的容器类型,使其行为看起来像一种不同的类型。标准库定义了三个序列容器适配器:stack、queue和priority_queue。

1. queue

queue是一个队列,实现先进先出功能,queue不是标准的STL容器,却以标准的STL容器为基础queue是在deque的基础上封装的。之所以选择deque而不选择vector是因为deque在删除元素的时候释放空间,同时在重新申请空间的时候无需拷贝所有元素。

queue 和 stack 有一些成员函数相似,但在一些情况下,工作方式有些不同:

  • front():返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
  • back():返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
  • push(const T& obj):在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。
  • push(T&& obj):以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。
  • pop():删除 queue 中的第一个元素。
  • size():返回 queue 中元素的个数。
  • empty():如果 queue 中没有元素的话,返回 true。
  • emplace():用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。
  • swap(queue &other_q):将当前 queue 中的元素和参数 queue 中的元素交换。它们需要包含相同类型的元素。也可以调用全局函数模板 swap() 来完成同样的操作。

和 stack 一样,queue 也没有迭代器。访问元素的唯一方式是遍历容器内容,并移除访问过的每一个元素。

Queue操作
q.pop() 移除q的首元素或priority_queue的最高优先级的元素,返回void
q.front() 访问首元素或尾元素
q.back() 访问尾元素,只适用于queue
q.top() 访问最高优先级元素,只适用于priority_queue 
q.push(item) 在queue末尾或priority_queue中恰当位置创造一个元素,其值为item, 或者又args构造
q.emplace(args) 同上

2. stack

stack是实现先进后出的功能,和queue一样,也是内部封装了deque。stack的源代码原理和实现方式均跟queue相同。

stack的定义:

int main(int argc, const char * argv[]) {
    
    //定义stack对象
    stack 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;
}
//定义类
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 s1;
    
    //入栈
    s1.push(t1);
    s1.push(t2);
    s1.push(t3);
    
    //出栈并打印
    while (!s1.empty()) {
        //打印栈顶元素
        Teacher tmp = s1.top();
        tmp.printT();
        
        //出栈
        s1.pop();
    }

    return 0;
}
stack操作
stk.top() 访问栈顶元素
stk.push(value) 将value压入栈内
stk.emplace(seq) 用seq构造元素并压栈
stk.pop() 将当前栈顶元素出栈,返回类型void

 

参考文档:

https://blog.csdn.net/u014465639/article/details/70241850

https://blog.csdn.net/u013443618/article/details/49964299

https://www.jianshu.com/p/497843e403b4

https://blog.csdn.net/v_JULY_v/article/details/6105630

https://blog.csdn.net/qq_37653144/article/details/79334725

你可能感兴趣的:(c++,数据结构,c++)