STL(标准模板库)(1)

STL,即标准模板库,它涵盖了常用的数据结构和算法,并且具有跨平台的特点。将泛型编程思想和STL库用于系统设计中,明显降低了开发强度,提高了程序的可维护性及代码的可重用性。
STL是C++标准函数库的一部分。STL的基本观念就是把数据和操作分离。

STL中数据由容器来加以管理,操作则由可制定的算法来完成。迭代器在容器和算法之间充当黏合剂,它使得任何算法都可以和任何容器进行交互运作。STL含有容器,算法,迭代器组件。

其中容器分为下面几种。
1)STL序列容器:vector,string,deque和list。
2)STL关联容器:set,multiset,map和multimap。
3)STL适配容器:stack,queue和priority_queue。

1.什么是STL?

STL(standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳与C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和算法,为广大C++程序员提供了一个可扩展的应用框架,高度体现了软件的复用性。它类似于Microsoft Visual C++中的MFC(Microsoft Foundation Class Library)。
从逻辑层次来看,在STL中体现了泛型化程序设计思想,引入了许多新的名词,比如需求,概念,模型,容器,算法,迭代器等。
从实现层次看,整个STL是以一种类型参数化的方式实现的,这种方式基于一个在早先C++标准中没有出现的语言特性——模板。如果查阅任何一个版本的STL源代码,就会发现,模板作为整个STL的基石是一件千真万确的事情。除此之外,还有许多C++的新特性为STL的实现提供了方便。
STL是最新的C++标准函数库的一个子集,它占据了整个库的大约80%的分量。而作为在实现STL过程中扮演关键角色的模板,则充斥了几乎整个C++标准函数库。C++标准函数库中的各个组件的关系如图。
这里写图片描述

2.具体说明STL 如何实现vector

vector内部是使用动态数组的方式实现的。如果动态数组的内存不够用,就要动态地重新分配,一般是当前大小的两倍,然后把原数组的内容拷贝过去。所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降。它的内部使用allocator类进行内存管理,程序员不需要自己操作内存。

3.看代码回答问题——vector容器中iterator的使用

vector<int>array;
array.push_back(1);
array.push_back(2);
array.push_back(3);
for(vector<int>::size_type i=array.size()-1;i>=0;--i)//反向遍历array数组
{
    cout<<array[i]<<endl;
}

当运行代码时,没有输出3 2 1,而是输出了一大堆很大的数字,为什么?
修改代码:

for(vector<int>::size_type j=array.size()-1;j>=0;j--)
{
    cout<<array[j-1]<<endl;
}

这样就输出了3 2 1。为什么呢?

由于vector支持随机访问,并且重载了[]运算符,因此可以像数组那样(比如a[j])来访问vector中的第i+1个元素。
现在来简单分析vector中size_type的定义过程。为方便起见,下面省略了某些头文件。
在vector定义中可以看到:

1   typedef _A::size_type  size_type;

而A是alloctor<_Ty>,因此查看alloctor的定义,不难发现:

1   typedef _SIZT  size_type;

而_SIZT的定义为:

1  #define _SIZT    size_t

最后size_t的定义为:

1  typedef  unsigned int size_t;

因此最后的结果为:size_type是个unsigned int类型成员。我们知道,无符号的整数是大于等于0的,因此上面第一段代码(代码中第5行)中的i>=0永远为true,程序一直循环,输出很多随机数,最后程序崩溃。而第二段代码(代码第一行)使用了i>0作为循环的条件,即i为0时结束循环。

4.看代码找错误——vector容器的使用

typedef vector Intarray;
IntArray array;
array.push_back(1);
array.push_back(2);
array.push_back(2);
array.push_back(3);
//删除array数组中所有的2
for(IntArray::iterator itor=array.begin();itor!=array.end();++itor)
{
    if(2==*itor)
        array.erase(itor);
}

这道题有两个错误。
1)代码第一行没有使用类型参数,这将会导致编译错误,由于array需要添加int类型的元素,因此代码第一行定义vector时因该加上int类型。

1    typedef  vector<int>  IntArray;

2)代码第8~12行的for循环,这里只能删除array数组中的第一个2,而不能删除所有的2。这是因为,每次调用“array.erase(itor);”后,被删除元素之后的内容会自动往前移,导致迭代漏项,应在删除一项后使itor–;使之从已经前移的下一个元素起继续遍历。

正确的代码如下:

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    typedef vector<int>  IntArray;
    IntArray array;
    array.push_back(1);
    array.push_back(2);
    array.push_back(2);
    array.push_back(3);
    //删除array中数组中所有的2
    for(IntArray::iterator itor=array.begin();itor!=array.end();++itor)
    {
        if(2==*itor)
        {
            array.erase(itor);
            --itor;//删除一项后使itor--
        }
    }

    //测试删除后array中的内容
    for(itor=array.begin();itor!=array.end();++itor)
    {
        cout<<*itor<<endl;
    }
}

这里写图片描述

5.把一个文件中的整数排序后输出到另一个文件中

(运用vector解决实际问题)
这个题目设计文件操作以及排序。我们可以使用vector容器来简化文件操作。在读文件的时候用push_back把所有的整数放入到一个vector对象中,在写文件时用[]操作符直接把vector对象输出到文件。代码如下:

#include<iostream>
#include<fstream>
#include<vector>
using namespace std;

//对data容器中的所有元素进行vector排序
void Order(vector<int>& data)
{
    int count=data.size();//获得vector中的元素个数
    for(int i=0;i<count;i++)
    {
        for(int j=0;j<count-i-1;j++)
        {
            if(data[j]>data[j+1])//如果当前元素比下一个元素大,则交换
            {//结果为升序排列
                int temp=data[j];
                data[j]=data[j+1];
                data[j+1]=temp;
               }
        }
    }
}

int main()
{
    vector<int>data;
    ifstream in("C:\\data.txt");
    if(!in)//打开输出文件失败
    {
        cout<<"infile error!"<<endl;
        return 1;
    }
    int temp;
    while(!in.eof())
    {
        in>>temp;//从文件中读取整数
        data.push_back(temp);//把读取的证书放入data容器中
    }
    in.close;
    Order(data);//冒泡排序
    ofstream  out("c:\\result.txt");
    if(!out)//打开输出文件失败
    {
        cout<<"outfile error!"<<endl;
        return 1;
    }
    for(int i=0;i<data.size();i++)
        cout<<data[i]<<" ";//把data容器中的所有元素输出至文件
    out.close();
    return 0;
}

6.list和vector有什么区别

vector和数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好地支持随机存取(使用[ ]操作符访问其元素)。但是由于它的内存空间是连续的,所以在中间进行插入和删除操作会造成内存块的拷贝(O(n))。另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。

list是由数据结构中的双向链表实现的,因此它的内存空间可以是不连续的。因此只能通过指针来进行数据的访问。这个特点使得它的随机存取变得非常没有效率,需要遍历中间的元素,搜索复杂度O(n),因此它没有提供[ ]操作符的重载。但由于链表的特点,它可以很好的效率支持任意地方的删除和插入。

由于list和vector上面的这些区别,list::iterator与vector::iterator也有一些不同。例子如下:

#include<iostream>
#include<vector>
#include<list>
using namespace std;

int main(void)
{
    vector<int> v;
    list<int> l;

    for(int i=0;i<8;i++)//往v和l中分别添加元素
    {
        v.push_back(i);
        l.push_back(i);
    }

    cout<<"v[2]="<<v[2]<<endl;
    //cout<<"l[2]="<<l[2]<<endl;//编译错误,list没有重载[]
    cout<<(v.begin()<v.end())<<endl;
    //cout<<(l.begin()<l.end())<<endl;//编译错误,list::iterator没有重载<或>
    cout<<*(v.begin()+1)<<endl;
    vector<int>::iterator itv=v.begin();
    list<int>::iterator it1=l.begin();
    itv=itv+2;
    //it1=it1+2;//编译错误,list::iterator没有重载+
    it1++;it1++;//list::iterator中重载了++,只能使用++进行迭代访问
    cout<<*itv<<endl;
    cout<<*it1<<endl;

    return 0;
}

由于vector用于一段连续的内存空间,能非常好地支持随机存取,因此vector::iterator支持“+”,“-”,“+=”,“<”等操作符。
而list的内存空间可以是不连续的,它不支持随机访问,因此list::iterator不支持“+”,“+=”,“<”等操作符运算。因此代码第20,26行会有编译错误。只能使用“++”进行迭代,例如代码第27行,使用两次itl++来移动itl。还有list也不支持[ ]运算符,因此代码第18行出现编译错误。
总之,如果需要高效的随机存取,而不在乎插入和删除的效率,就使用vector;如果需要大量的插入和删除,而不关心随机存取,则应使用list。

7.分析代码问题并修正——list和vector容器的使用

(理解vector和list的使用)

#include<iostream>
#include<vector>
#include<list>
using namespace std;

int main(void)
{
    list list1;
    for(int i=0;i<8;i++)
    {
        list1.push_back(i);
    }
    for(list::iterator it=list1.begin();it!=list1.end();++it)
    {
        if(*it%2==0)
        {
            list1.erase(it);
        }
    }
    return 0;
}

有两个方面的问题。
第一个问题是list1以及它的iterator都缺少类型参数,代码第8行和第15行都有编译错误。由于在代码第12行添加的元素类型为int,因此需要将第8行和第15行中的list::改成list::。

第二个问题是当改正了上面编译的错误后,运行程序会导致程序崩溃。因为当第一次执行代码第19行(调用erase方法删除元素)后,it原来指向的元素内存已经被释放了,因此进入下一次循环后,获得it的值就出现了访问违规。

这里要注意:
vector使用的是数组方式,当删除一个元素后,此元素的后面元素会自动往前移动。而list由于使用链表结构,因此执行erase时会释放链表的结点内存。但是可以通过erase的返回值获得原来链表的下一个元素。
改正后代码如下:

#include<iostream>
#include<vector>
#include<list>
using namespace std;

int main(void)
{
    list<int> list1;//指定存放int类型的元素
    for(int i=0;i<8;i++)
    {
        list1.push_back(i);//把整数放入list1中
    }
    //iterator也需要指明是int类型元素的迭代器
    for(list<int>::iterator it=list1.begin();it!=list1.end();++it)
    {
        if(*it%2==0)
        {
            list1.erase(it);//得到下一个元素的位置
            it--;//回到上一个元素位置
        }
    }
    for(it=list1.begin();it!=list1.end();++it)
    {
        cout<<*it<<endl;//输出list1内的各个元素
    return 0;
}

执行了代码第10~13行,0-8这9个数字被放入list1中。这里简单分析一下删除过程。当it指向第一个元素,即0时,由于0是2的倍数,因此18行的if判断为真,执行删除操作,it返回下一个元素位置,即指向原来的1。为了让进入下一下循环时it还指向1,需要让it–(不能使用it=it-1)。
这里写图片描述

8.stl::deque是一种什么数据类型

(理解deque容器的内部结构)
deque是由一段一段定量的连续空间组成的,因此是动态数组类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值