STL容器类和迭代器

STL容器类介绍

        容器,顾名思义就是盛放东西的东西,如盛饭的饭碗就是一个容器,这样的容器c++库也为我们提供了,只是不能用来盛饭。在程序中只有数据和数据处理方法,数据和方法结合在一起就形成了类或对象,在面向对象的语言中流行一切皆是对象,要有对象首先肯定要有类,用来存放对象的类就叫容器类,简称容器。

        计算机的运行时数据只能存放在内存中,而容器类就是用做容器的内存管理方法。容器的内涵就是数据结构(数据打包的方法)+算法(数据处理方法)。

        容器其实我们早就接触到了,c中的数组、结构体就是个容器,只是c中的这个容器内部只有数据,没有方法。

        c++容器,通过类库方式提供,容器的类库被模板技术泛化后就是STL(stande tempelate libriy)容器库。学STL的核心就是学类库的容器类,跟我们学习使用c中的stdio库时一个道理。

STL容器分类

        STL容器比较多,大致可以分为序列容器、排序容器、哈希容器三类。

序列容器

        元素在容器中的位置与值无关,即容器不是排序的,类似于数组存放的数据是无序的。包括array、vector、list、forward_list。

排序容器

        排序容器就是插入数据的时候,自动按照从小到大排列好,这种排序肯定需要开销的,好处就是我们要使用的时候不用再去排序,他已经自动排好了,我们直接用即可。包括set、multiset、map、mutilmap等。

哈希容器

        哈希是hash英文的发音,哈希容器中的元素是未排序的,元素位置由哈希函数确定,遵守一定规则的<key,value>对式存储,key代表参数类型,value代表具体的值,如描述一个长方形:<长,25>,<宽,30>,包括unordered_set、 unordered_map、 hash_set hash_multiset、hash_multimap。

容器类如何学

        STL的核心就是容器类,STL的其它技术都是围绕容器类展开。STL的本质就是一套模板技术泛化类型的c++基本数据结构和算法的类库。学习STL就是学会每种容器的使用,根据需求知道那种容器类比较合适。

array容器类

        array容器其实就是c++使用template对c语言的数组进行的二次封装。具有数组的定长、同类型元素、在内存中连续排布的特点。

头文件:<array>

定义并初始化

template<class T,std::size_t N>struct array  //模板结构体
array<int, 2>  a, b;                  //定义但不初始化,可定义1个或多个
array<int, 3> a1{1,2,3};              //聚合初始化,错误示例array<int, 2>  a(1,2)
array<int, 4> a2={1,2,3,4};           //调用带参构造函数聚合初始化
array<int, 4>  a3=a2;                 //调用拷贝构造函数

元素访问

        operator[ ]方式: a[0]=1或者int b=a[0];       //[ ]运算符重载
        调用at函数:       a.at(2)=4 或 b= a.at(2);      //调用类中的方法at
        front函数:         int b = a.front或a.front=5; //读写数组的第一个元素
        back函数:         int b = a.back;                    // 读写数组的最后一个元素
        date函数:          int *p=a.date;                     //获取存放数据的首地址

容量

        empty函数:         if(a.empty()==true);        //检查是否为空,空1,非空0
        size函数:             int b=a.size();                 //返回容纳元素数量
        max_size函数:    int b=a. max_size();        //返回最大可容纳元素的数量

操作

        fill函数:       a.fill(6);          //全部填充指定值6
        swap函数:  a.swap(b);               //a容器与b容器内的array数据交换

非成员函数

        operator==   if(a=b);          //判断两个array容器内的值是否相等,是turn,否false

  get函数:      

        template< size_t I, class T, size_t N > T& get(array<T,N>& a ) noexcept; //原型
        int b = get<1>(a) 或 int b = get<1,int,5>(a);  //获取a容器第1个元素
        get<1,int,5>(a)=33 或 get<1>(a)=33;          //向a容器第1个元素写入33

swap函数:

​
        template<class T, size_t N>
        void swap( array<T,N>& lhs,array<T,N>& rhs ); //两行原型
         swp<int,5>(a,b);或swp (a,b) //实际上可以通过对a和b的反推

辅助类

tuple_size

        作用:在编译阶段获取到传入的数组的大小,主要应用于写类库的人不知道将来用户会传入多长的数组,但是在其内部又需要为其申请空间,用该语句占位拖延,待编译到调用本类时再根据传入的数组长度来确定大小。

原型:template< typename T, size_t N >
struct  tuple_size<array<T, N> > : integral_constant< size_t, N>
{
};

        是一个模板类,根据传入的array容器类对象,推导出T,N,还 private(默认)继承了integral_constant< size_t, N>模板类。在tuple_size类中定义了一个静态成员变量value,他记录的是传入array容器内数组的长度。

示例:

array<int, 5> a;
cout << "size= " << tuple_size<a> :: value << endl;     //编译时获得
cout << "size= " << a.size() << endl;                           //运行时获得

array容器传参

        模板类的关键就是根据传参,推导数据类型,如果是一个类,同样可以利用推导的结果来定义一个与传参相同类型的对象,示例如下:      

template<typename T,size_t N> void func(array<T,N>)  //通过传参获取T,N。
{
  array<T,N> aa;
  cout<<"size= "<<aa.size()<<endl;
}
array<int,6> a;  //定义一个array容器
func(a);             //将容器类传给函数func

tuple_element

        作用:获取容器中指定元素的类型;

      原型:template<size_t I, class T, size_t N > struct tuple_element<I, array<T, N> >;

    使用:

using T=tuple_element<0, decltype(data)>::type  //using T起typedef效果;
is_same<t,int>                   // is_same在标准库,判断两个类型相同true,否false

上式中decltype时c++关键字,作用是获得表达式的类型。

迭代器

        迭代,升级迭代,去旧用新的意思,包含了从最初的版本一次次的升级迭代,本质上是一种遍历行为,其实迭代器底层就是通过移动指针来遍历处理的一种机制。

        实际上我们也早都接触过迭代行为,在c中我们遍历数组使用*p++,指针变量就是一种遍历迭代器。但是指针不能用来直接p++来访问结构体内部元素,就需要一种更高级的迭代器。

        所有迭代器都共同继承了一个基类(接口),这个基类规定迭代器的一些基本行为,如++;--这种基本操作。每一个容器内都包含了一个专属的迭代器成员类iterator,将该类实例化后通过对象就可以直接对该对象进行++、--就可对STL容器进行遍历。通常我们定义这个变量的时候习惯将名称写为iter,当然写其他的也可以,使用示例如下。       

array<int, 5>  a= {1,2,3,4,5};   //定义一个容器类
array<int, 5> :: iterator  iter;     //定义一个迭代指针iter
for(iter=a.begin(); iter != a.end();iter++) //判断不用小于,使用!=
{
   cout<<iter<<" = " << *iter <<endl; //读
}

式中begin( )和end( )是array容器类中的函数,分别返回起始迭代器和结束迭代器。

const与非const

        出于保护的数据的目的,c++还提供了cbegin与cend带cosnt的只读迭代器,begin与end返回的是可读写的迭代器,而cbegin、cend只读迭代器也就意味着我们对容器的元素只能读不能写。

迭代器内还包含了迭代器指针iterator和constiterator,iterator用于接收begin、end返回的地址,cosnt_iterator用来接收cbegin与cend对应的指针类似是const_iterator,他们必须配套使用,示例如下:

array<int, 5>::const_iterator iter;
for(iter=a.cbegin(); iter != a.cend();iter++)
{
cout<<iter<<" = " << *iter <<endl;
}

        在上例中涉及的cosnt_iterator与iterator有各自对应的迭代器,交叉使用容易出错,所以有的人干脆就不定义iter指针,直接使用auto来自动推导,示例如下:

for(auto iter=a.cbegin(); iter != a.cend();iter++) //自动推导,cbegin/begin
{
cout<<iter<<" = " << *iter <<endl;
}

        begin返回的是第0个元素的迭代器,而end返回的是最后一个的下一个元素的地址。如下图,这样做的目的是方便我们去遍历完所有元素,要强调的是在遍历时for循环中不要写“<”,因为在链表中,地址不是连续排列的,尾地址不一定比前面的地址编号大,推荐写“!=”。

逆向迭代器

        rbegin与rend返回逆向迭代器,逆向迭代器的rbegin返回的是末尾元素,而rend返回的是首元素的前一个地址,逆向迭代器的++是向前移,--是向后移动,就像挂倒挡给油是向后移动,示例如下。

        不管是正向还是逆向迭代器,都遵循“++不到end;--不到begin”。++和--不能单纯的从符号上按常规思维来理解,本质上还是对++和--的运算符重载,所以取决于函数实现方法。

STL的不同类型的迭代器

InputIterator

        输入迭代器,只能从容器内读出而不能向容器内写入,只能单次读出(二次读取不保证仍然可二次读取,就像串口接收缓存),只能++不能--(单向的),不能保证二次遍历容器时顺序不变,输入迭代器适用于单通(单向)只读型算法。

outputiterator

        输出迭代器,用于将信息传输给容器(修改容器值),但是不能读取(如显示器就是一个典型的只写不读的的设备,可以用输出容器来表示),只能++不能--(单向的),输出迭代器适用于单通只写型算法。

forwarditerator

        前向迭代器,只能++不能--(单向的)

bidireciterator

        双向迭代器,即能++也能--,双向移动

randomaccessiterator

        随机访问迭代器,能双向移动,且可以单出跨越多个元素移动

contiguousiterator

        连续迭代器,所指向的逻辑相令元素,在物理上也相邻,如数组。

基于范围的for

用作:对容器中的各个元素进行遍历,可读性更高,书写更简单。

语法:for ( 初始化语句(可选)范围变量声明 : 范围表达式 ) 循环语句                  

array<int,5> aa={1,2,3,4,5};
for(int iter: aa)
{
cout << iter<<endl;
}

指定元素类型必须与对象元素类型相同,实际大多使用auto自动推导,示例如下:

for(auto iter :  aa)
{
cout << iter<<endl;
}

vector容器

        vector是动态数组,可以按需扩展和收缩数组大小,实际上vecrot在C99版的c中就已经有了,只是我们很少用,vector是数组的升级,vector容器是array容器的升级版,本质上都是数组,具有数组的共同特性,vector新增了一个动态扩展和收缩的功能,这个扩展和收缩是自动的,我们无需去干预,内部实现原理就是当我们申请一个数组时,他分配的空间会大于我们要申请的空间,以预留给我们扩容,当预留的空间用完了,下次再扩展时,他就只能重新分配一个更大的空间,再把之前的数据拷贝过去,所以vector的地址是动态的,我们不能使用指针去访问,应该使用迭代器去访问,因为他在迁移时会更新迭代器的begin和end。

头文件:vector

定义和初始化:

vector<类型>  对象

vector<int>         aa={1,2,3,4,5};     //定义并赋初值
vector<int>         aa{1,2,3,4,5};        //定义并赋初值
vector<int>         bb(aa);                //定义并调用拷贝构造函数
vector<string>    bb(5, ”test”);       //定义并填充5个相同元素sest

元素访问

at函数 : aa.at(4)=80或 int b= aa.at(4);      //读写5个元素
operator[ ]     aa[4]=80或 int b=aa[4];           //读写第5个元素
front函数:      int b = a.front或a.front=5; //读写数组的第一个元素
back函数:         int b = a.back;                    // 读写数组的最后一个元素
date函数:          int *p=a.date;                     //获取存放数据的首地址

迭代器

begin( )与cbegin( )             auto iter = a.begin( ); //获取起始迭代指针
end( ) 与cend( )                 auto iter = a.end( );   //获取起始迭代指针
rbegin( )与crbegin( )           auto iter =a.begin( );  //获取起始迭代指针

容量

empty():   if(a.empty()==true);      //检查是否为空,空1,非空0
size():     int b=a.size();                //返回容纳元素数量
max_size(): int b=a. max_size();      //返回最大可容纳元素的数量
reserve():       a.reserve(50);               //a容器底层数组分配50个元素大小
capacity( )      int b=a.capacity();        //获取a容器底层数组大小。

修改器

clear( )     a.clear()                          //清空底层数组元素
insert()     auto T=aa.begin(); aa.insert(T+0,90); //插入
erase()      a.erase(a.begin() +1);      //擦除第1个元素
push_back()  a.push_back(55);               //将55添加的末尾
pop_back()   a.pop_back();                   //移除末尾元素
shrink_to_fit() a. shrink_to_fit()           //释放底层数组未使用的多余分配空间

遍历

for(auto c: cc)
{

   cout << c <<endl;
}

list容器

        前面介绍的array、vector都是对普通数组、动态数组的封装,而list则是对链表的封装,具有链表的可在任何时间任何位置插入节点数据、物理地址不一定连续等特点,正因如此所以不支持快速访问,必须重头或者尾遍历到目标节点,与forward_list(单向链表容器)比较,移动更方便,但是占用内存也更多,主要是双向链表多了一个前指针。因为节点的前后由指针链接,所以中间节点的移除不会影响到头尾迭代器。

定义和初始化

list<类型>  对象
list <int>      aa={1,2,3,4,5};     //定义并赋初值
list <int>      aa{1,2,3,4,5};        //定义并赋初值
list <int>      bb(aa);                //定义并调用拷贝构造函数
list <string>  bb(5, ”test”);       //定义并填充5个相同元素sest

元素访问

front()     aa.front()+1; //访问第2个元素
back()     aa.back()-1;   //访问倒数第2个元素

迭代器

begin( )与cbegin( )             auto iter = a.begin( ); //获取起始迭代指针
end( ) 与cend( )                 auto iter = a.end( );     //获取起始迭代指针
rbegin( )与crbegin( )           auto iter =a.begin( );   //获取起始迭代指针

容量

empty():   if(a.empty()==true);             //检查是否为空,空1,非空0
size():     int b=a.size();                       //返回容纳元素数量
max_size(): int b=a. max_size();             //返回最大可容纳元素的数量

修改器

clear( )      a.clear()                                       //清空底层数组元素
insert()      auto T=aa.begin(); aa.insert(T+0,90);           //插入节点前插
erase()       a.erase(a.begin());                             //擦除第0个元素
push_back()   a.push_back(55);                                //将55添加的末尾
pop_back()    a.pop_back();                                   //移除末尾元素

操作

sort()          a.sort();        //链表值升序排序1、2、3、4、5
reverse()       a. reverse();    //将所有元素反转5、4、3、2、1
merge()         a.maerge(b);     //将b按升序合并,使用前必须分别升序排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值