【ThinkingInC++】74、通用容器

第七章 通用容器

7.1 容器和迭代器

一个好的面向对象的编程语言都伴随着一个容器集

 

一个vector用于高效地访问其中的所有元素,而一个链表list则用于高效地在其中的所有位置上进行插入操作。

 

7.2 概述

 

Copy函数

 

现在我们来看看变易算法。所谓变易算法(Mutating algorithms)就是一组能够修改容器元素数据的模板函数,可进行序列数据的复制,变换等。

我们现在来看看第一个变易算法:元素复制算法copy。该算法主要用于容器之间元素的拷贝,即将迭代器区间[first,last)的元素复制到由 复制目 标result给定的区间[result,result+(last-first))中。下面我们来看看它的函数原型:

1     template<class InputIterator, class OutputIterator>  
2        OutputIterator copy(  
3           InputIterator _First,   
4           InputIterator _Last,   
5           OutputIterator _DestBeg  
6        );  

参数

_First, _Last

指出被复制的元素的区间范围[ _First,_Last).

_DestBeg 

指出复制到的目标区间起始位置

返回值

返回一个迭代器,指出已被复制元素区间的最后一个位置

istream_iterator和ostream_iterator

 

istream_iterator<T> in(strm);   (其中T指明此istream_iterator的输入类型 , strm为istream_iterator指向的流)

 

ostream_iterator是流迭代器。
流迭代器是标准模板库中的。因此是类模板。
ostream_iterator<int>
指定了类型,就是迭代器读写的类型。
通过这个流迭代器可以把你要输入的写入到指定的流中。
cout就是指定的流。就是标准输出。
可以改成一个输出流就可以,比如一个文件。
通俗的一点说,你把它看成一个指向输出流的指针。通过这个指针你可以把东西写的输出流中。
copy (v.begin(),v.end(),output);
这个意思就是说,把向量V中的数据放到cout输出流中,通过流迭代器output.
ostream_iterator<int> output(cout ,"*");
这个的意思说,放到输出流的时候,没放一个整数,就末尾添加一个*.

你可以运行下程序加深理解
#include <vector>
#include <iostream>
#include <iterator>
using namespace std;

int main()
{
    vector<int> v;
 v.push_back(1);
 v.push_back(2);
 ostream_iterator<int> output(cout,"*");
 copy(v.begin(),v.end(),output);
 return 0;
}
 
WordSet.cpp
 
/**
* 书本:【ThinkingInC++】
* 功能:显示包含在一个文档中的单词清单
* 时间:2014年10月18日19:05:50
* 作者:cutter_point
*/
 
#include <fstream>
#include <iostream>
#include <iterator>
#include <set>
#include <string>
#include "../require.h"
 
using namespace std;
 
//显示文件中的全部单词
void wordSet(const char* fileName)
{
    ifstream source(fileName);  //打开这个文件,输入流
    assure(source, fileName);
    string word;    //用来存放每个单词
    set<string> words;  //存放所有的单词
    while(source >> word)
        words.insert(word);
    //这里使用一个通用算法copy
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n")); //就是把范围内的内容写到流里面去
 
    cout<<"那些独一无二的单词的个数:"<<words.size()<<endl;
}
 
int main(int argc, char* argv[])
{
    if(argc>1)
        wordSet(argv[1]);   //打开这个文件
    else
        wordSet("wordSet.cpp");
 
    return 0;
}
 

7.2.1 字符串容器

STL容器确保在其自身被销毁时将调用其包含的每个对象的析构函数。然而,指针并没有析构函数,因此用户必须自己用delete删除它们。

 

 

7.2.2 从STL容器继承

面向对象设计准则更倾向于使用组合(成员函数)而不是继承。

 

FileEditor.h

 

/**
* 书本:【ThinkingInC++】
* 功能:构造函数打开文件,将其读入到这里,再用成员函数write()将包含string的vector写入任何
*       一个输入流ostream
* 时间:2014年10月18日19:06:32
* 作者:cutter_point
*/

#ifndef FILEEDITOR_H_INCLUDED
#define FILEEDITOR_H_INCLUDED

#include <iostream>
#include <string>
#include <vector>

class FileEditor : public std::vector<std::string>
{
public:
    //覆盖一些,或者添加一些函数
    void open(const char* filename);
    FileEditor(const char* filename) { open(filename); }
    FileEditor() {};    //这个默认构造函数

    void write(std::ostream& out=std::cout);    //out就是输出流
};

#endif // FILEEDITOR_H_INCLUDED


 

 

FileEditor.cpp
/**
* 书本:【ThinkingInC++】
* 功能:构造函数打开文件,将其读入到这里,再用成员函数write()将包含string的vector写入任何
*       一个输入流ostream
* 时间:2014年10月18日19:07:10
* 作者:cutter_point
*/

#include "FileEditor.h"
#include <fstream>
#include "../require.h"

using namespace std;

void FileEditor::open(const char* filename)
{
    ifstream in(filename);
    assure(in, filename);
    string line;    //每一行字符串
    while(getline(in, line))
        push_back(line);    //把文件的全部都保存到vector里面去
}


//    void write(std::ostream& out=std::cout);    //out就是输出流
void FileEditor::write(ostream& out)
{
    for(iterator w=this->begin() ; w != this->end() ; ++w)
    {
        out<<*w<<endl;
    }
}


 

FEditTest.cpp

 

/**
* 书本:【ThinkingInC++】
* 功能:读取文件
* 时间:2014年10月18日19:07:36
* 作者:cutter_point
*/

#include <sstream>
#include "FileEditor.cpp"
#include "../require.h"

using namespace std;

int main(int argc, char* argv[])
{
    FileEditor file;
    if(argc > 1)
        file.open(argv[1]);
    else
    {
        cout<<"程序创建一个文件用来使用!"<<endl;
        file.open("FEditTest.cpp");
    }

    //把文件输出,并且写出序号
    int i=1;
    FileEditor::iterator w=file.begin();    //迭代器,指向文件的开始,其实是vector的开始
    while(w != file.end())
    {
        ostringstream ss;
        ss<<i++;
        *w=ss.str()+"\t: "+*w;  //修改vector里面的值
        ++w;
    }
    //打印出来
    file.write();

    return 0;
}


 

7.3.1 可逆容器中的迭代器

一个容器可以是可逆的,从尾部反向移动的迭代器,所有标准都支持这个

成员函数rbegin(){容器尾部的迭代器,reverse_iterator }和rend(){这个产生一个“超越起始的迭代器”}

 

Reversible.cpp

 

/**
* 书本:【ThinkingInC++】
* 功能:关于逆向迭代器
* 时间:2014年10月18日19:08:03
* 作者:cutter_point
*/

#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "../require.h"

using namespace std;

int main()
{
    ifstream in("Reversible.cpp");  //打开文件
    assure(in, "Reversible.cpp");
    string line;
    vector<string> lines;   //保存文件的内容
    while(getline(in, line))
        lines.push_back(line);
    //输出,但是反向
    for(vector<string>::reverse_iterator r=lines.rbegin() ; r != lines.rend() ; ++r)
        cout<<*r<<endl;

    return 0;
}


 

7.4 基本序列容器:vector、list和deque

 

向量容器(vector)是一种顺序容器,是一块连续分配的内存,支持随机访问,从数据安排的角度看,和数组极其相似,数组跟vector的区别在于:数组是静态分配空间,一旦分配了空间的大小,就不可以再改变了,例如,inta[6];而vector是动态分配内存,随着元素的不断插入,它会按照自身的一套机制不断扩充自身的容量,vector容器的容量增长是按照容器现在容量的一倍进行增长。

 

begin函数:

函数原型:

iterator begin();

const_iterator begin();

功能:

返回一个当前vector容器中起始元素的迭代器。

 

end函数:

函数原型:

iterator end();

const_iterator end();

功能:

返回一个当前vector容器中末尾元素的迭代器。

 

front函数:

函数原型:

reference front();

const_reference front();

功能:

返回当前vector容器中起始元素的引用。

 

back函数:

函数原型:

reference back();

const_reference back();

功能:

返回当前vector容器中末尾元素的引用。

 

vector::assign

vector::assign //用来构造一个vector的函数,类似于copy函数
void assign( size_type _Count, const Type& _Val);


//_Count指要构造的vector成员的个数,   _Val指成员的数值,他的类型必须与vector类型一致!
template<class InputIterator>
void assign( InputIterator _First, InputIterator _Last );
//两个指针,分别指向复制开始和结束的地方!

 

迭代器(iterator) 逆向迭代器(reverse_iterator)

 

Reserve

是容器预留空间,但并不真正创建元素对象,在创建对象之前,不能引用容器内的元素,因此当加入新的元素时,需要用push_back()/insert()函数。

Resize

是改变容器的大小,并且创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。

再者,两个函数的形式是有区别的,reserve函数之后一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小,第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数

 

关于insert
设iter指向A,
iter=iver.insert(iter,42); 
这句话的结果是在 老iter  (A) 的前面加元素(B),
然后新iter指向这个新加的元素(B)。
 
iterator insert( iterator loc, const TYPE &val );
  void insert( iterator loc, size_type num, const TYPE &val );
  void insert( iterator loc, input_iterator start, input_iterator end );

insert() 函数有以下三种用法:

  • 在指定位置loc前插入值为val的元素,返回指向这个元素的迭代器,
  • 在指定位置loc前插入num个值为val的元素
  • 在指定位置loc前插入区间[start, end)的所有元素 .
 
关于erase
 
  iterator erase( iterator loc );
  iterator erase( iterator start, iterator end );
erase函数要么删作指定位置loc的元素,要么删除区间[start, end)的所有元素.返回值是指向删除的最后一个元素的下一位置的迭代器.前面的要删除,后面的不删除
 
BasicSequenceOperations.cpp
 
演示所有的基本序列容器:vector,deque,list所支持的操作

 

/**
* 书本:【ThinkingInC++】
* 功能:演示所有的基本序列容器:vector,deque,list所支持的操作
* 时间:2014年10月18日19:09:34
* 作者:cutter_point
*/

#include <deque>
#include <vector>
#include <list>
#include <iostream>

using namespace std;

//输出容器各种信息的函数
template<typename Container>
void print(Container& c, char* title="")
{
    //输出这个标题
    cout<<title<<":"<<endl;
    //判定这个容器是不是为空
    if(c.empty())
    {
        cout<<"(empty)"<<endl;
        return;
    }

    typename Container::iterator it;    //对应的迭代器,还有typename声明了这个Container是一个类型
    for(it=c.begin() ; it != c.end() ; ++it)    //全部输出
        cout<<*it<<" ";
    cout<<endl;
    //输出各种信息
    cout<<"size() "       <<c.size()
        <<"  max_size() " <<c.max_size()
        <<"  front() "    <<c.front()
        <<"  back() "     <<c.back()
        <<endl;
}

template<typename ContainerOfInt>
void basicOps(char* s)
{
    cout<<"-------------------------"<<s<<"-------------------------"<<endl;
    typedef ContainerOfInt Ci;
    Ci c;   //创建一个容器对象
    print(c, "这里c是使用了默认的构造函数");

    Ci c2(10, 1);   //创建10个元素,值都是1
    print(c2, "构造函数c2(10, 1)");

    int ia[]={1, 3, 5, 7, 9};
    const int IASZ=sizeof(ia)/sizeof(*ia);  //求得数组长度
    Ci c3(ia, ia+IASZ);
    print(c3, "这里调用的构造函数式两个指针c3(ia, ia+IASZ)");

    Ci c4(c2);  //拷贝构造函数
    print(c4, "调用拷贝构造函数c4(c2)");

    c=c2;   //vector::assign 用来构造一个vector的函数,类似于copy函数
    print(c, "调用赋值构造操作符c=c2");

    c.assign(10, 2);    //拷贝10个2进入容器
    print(c, "拷贝10个2进入容器c.assign(10, 2)");

    c.assign(ia, ia+IASZ);  //分别指向复制开始和结束的地方
    print(c, "分别指向复制开始和结束的地方c.assign(ia, ia+IASZ)");

    cout<<"---------------------------------------------------"<<endl;
    cout<<"c使用反向迭代器:"<<endl;

    typename Ci::reverse_iterator rit=c.rbegin();   //反向迭代器
    while(rit != c.rend())
        cout<<*rit++<<" ";  //反向全部输出
    cout<<endl;

    c.resize(4);    //改变容器容量大小
    print(c, "c.resize(4);    //改变容器容量大小");

    c.push_back(47);    //插入47
    print(c, "c.push_back(47);    插入47");

/*
    c.pop_back(47); //删除47
    print(c, "c.pop_back(47); 删除47");
*/

    c.pop_back();   //去除末尾的元素
    print(c, "c.pop_back();   去除末尾的元素");

    //正向
    typename Ci::iterator it=c.begin();
    ++it; ++it;  //向后移动两位
    c.insert(it, 74);   //向it指向的前面插入74
    print(c, "c.insert(it, 74);   向it指向的前面插入74");

    it=c.begin();
    ++it;
    c.insert(it, 3, 96);    //在it指向插入3个96
    print(c, "c.insert(it, 3, 96);    在it指向插入3个96");

    it=c.begin();
    ++it;
    c.insert(it, c3.begin(), c3.end()); //在it之前插入区间内容
    print(c, "c.insert(it, c3.begin(), c3.end());  在it之前插入区间内容");

    it=c.begin();
    ++it;
    c.erase(it);    //删除it指向的内容
    print(c, "c.erase(it);    //删除it指向的内容");

    typename Ci::iterator it2=it=c.begin();
    ++it;
    ++it2; ++it2; ++it2; ++it2; ++it2;
    c.erase(it, it2);   //删除区间内的内容,删除t1指向的但是不删除it2指向的内容
    print(c, "c.erase(it, it2);   //删除区间内的内容,删除t1指向的但是不删除it2指向的内容");

    c.swap(c2); //交换c和c2
    print(c, "c.swap(c2); //交换c和c2");

    c.clear();
    print(c, "清理容器c的全部");

}

int main()
{
    basicOps<vector<int> >("vector");
    basicOps<deque<int> >("deque");
    basicOps<list<int> >("list");

    return 0;
}


 

关于头文件中algorithm的算法generate_n

generate()

功能:用指定函数对象产生的值去给容器指定范围内元素赋值

  1. template<class ForwardIterator, class Generator>  
  2.    void generate(  
  3.       ForwardIterator _First,   
  4.       ForwardIterator _Last,   
  5.       Generator _Gen  
  6.    );  
generate_n()

功能:一个函数对象产生的值给一定的范围内指定数目的容器元素赋值

  1. template<class OutputIterator, class Diff, class Generator>  
  2.    void generate_n(  
  3.       OutputIterator _First,   
  4.       Diff _Count,   
  5.       Generator _Gen  
  6.    );  

 

7.4.6 链表

List以一个双向链表数据结构来实现

 

1.    特殊的list操作

一个unique()成员函数将从list中删除所有重复的对象,唯一的条件就是首先对list进行排序

 

UniqueList.cpp

 

/**
* 书本:【ThinkingInC++】
* 功能:一个unique()成员函数将从list中删除所有重复的对象,唯一的条件就是首先对list进行排序
* 时间:2014年10月18日19:10:07
* 作者:cutter_point
*/

#include <iostream>
#include <list>
#include <iterator>

using namespace std;

int a[]={1, 3, 1, 4, 1, 5, 1, 6, 1 };  //测试数据

const int ASZ=sizeof a/sizeof *a;   //求得数组的长度

int main()
{
    ostream_iterator<int> out(cout, " ");
    list<int> li(a, a+ASZ); //把范围内的内容代入到list链表中
    li.unique();    //删除重复对象
    copy(li.begin(), li.end(), out);    //把范围内的内容拷贝到输出流迭代器
    cout<<endl;

    //可以看到没有排序是无法进行删除重复项的
    li.sort();  //调用排序的函数,从小到大
    copy(li.begin(), li.end(), out);    //把范围内的内容拷贝到输出流迭代器
    cout<<endl;

    //再次去除重复项
    li.unique();
    copy(li.begin(), li.end(), out);    //把范围内的内容拷贝到输出流迭代器
    cout<<endl;

    return 0;
}


 

7.11 将STL容器联合使用

 

inserter

Visual Studio 2013

可讓您使用 inserter(_Cont,_Where) 而 insert_iterator<Container>(不是_Cont,_Where)的 Helper 樣板函式。

template<class Container>
    insert_iterator<Container> inserter(
        Container& _Cont,
        typename Container::iterator _Where
    );
參數

_Cont

新項目會加入的容器。

_Where

找出問題的插入的Iterator。

 

// iterator_inserter.cpp
// compile with: /EHsc
#include <iterator>
#include <list>
#include <iostream>
 
int main( )
{
   using namespace std;
   int i;
   list <int>::iterator L_Iter;
 
   list<int> L;
   for (i = 2 ; i < 5 ; ++i )  
   {
      L.push_back ( 10 * i );
   }
 
   cout << "The list L is:\n ( ";
   for ( L_Iter = L.begin( ) ; L_Iter != L.end( ); L_Iter++ )
      cout << *L_Iter << " ";
   cout << ")." << endl;
 
   // Using the template version to insert an element
   insert_iterator<list <int> > Iter( L, L.begin ( ) );
   *Iter = 1;
 
   // Alternatively, using the member function to insert an element
   inserter ( L, L.end ( ) ) = 500;
 
   cout << "After the insertions, the list L is:\n ( ";
   for ( L_Iter = L.begin( ) ; L_Iter != L.end( ); L_Iter++)
      cout << *L_Iter << " ";
   cout << ")." << endl;
}

 

清單 L 為:
 ( 20 30 40 ).
在插入之後,清單 L 為:
 ( 1 20 30 40 500 ).

 

Thesaurus.cpp

 

/**
* 书本:【ThinkingInC++】
* 功能:实现将STL容器联合使用
* 时间:2014年10月18日19:11:02
* 作者:cutter_point
*/

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <ctime>
#include <cstdlib>

using namespace std;

//Thesaurus将一个string(单词)映射到一个vector<string>(同义词>
typedef map<string, vector<string> > Thesaurus;
//TEntry是Thesaurus中的一个条目
typedef pair<string, vector<string> > TEntry;
//迭代器
typedef Thesaurus::iterator TIter;

//重写一个输出流
namespace std
{//重写操作符<<
    ostream& operator<<(ostream& os, const TEntry& t)
    {
        os<<t.first<<": ";
        //拷贝算法
        copy(t.second.begin(), t.second.end(), ostream_iterator<string>(os, " "));
        return os;
    }
}

//一个产生文章的thesaurus的元素
class ThesaurusGen
{
    static const string letters;    //
    static int count;   //一个单词的长度
public:
    int maxSize() { return letters.size(); }    //最大的长度
    TEntry operator()() //重载()符号
    {
        TEntry result;  //一个将要返回的对象,暂时为空
        if(count >= maxSize())
            count=0;    //如果产生的对象比最大的还多就重置为0
        result.first=letters[count++];  //把这个字符添加给pair的第一个位置
        int entries=(rand() % 5)+2; //这是这个单词的同义的个数
        for(int i=0 ; i<entries ; ++i)
        {
            int choice=rand()%maxSize();    //产生一个随机数,表示添加的同义词是哪个
            char cbuf[2]={0};
            cbuf[0]=letters[choice];    //这个是选择出来的字符,随机选择
            result.second.push_back(cbuf);  //把这个字符添加进去
        }
        return result;
    }
};

//初始化静态数据成员
int ThesaurusGen::count=0;
const string ThesaurusGen::letters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");

//显示一个单词
string menu(Thesaurus& thesaurus)
{
    while(true)
    {
        cout<<"选择一个单词,从0开始到结尾"<<endl;
        for(TIter it=thesaurus.begin() ; it != thesaurus.end() ; ++it)
            cout<<(*it).first<<' ';
        cout<<endl;
        string reply;
        cin>>reply; //输入一个单词
        if(reply.at(0) == '0') exit(0); //如果输入的第一个是0的话
        //否则在thesaurus里面找,看是否有对应的
        if(thesaurus.find(reply) == thesaurus.end())
            continue;
        //找到了以后返回
        return reply;
    }
}

int main()
{
    srand(time(0)); //设置随机数的种子
    Thesaurus thesaurus;
    //产生10个数给这个容器
    generate_n(inserter(thesaurus, thesaurus.begin()), 10, ThesaurusGen());
    //把全部的打印出来,调用命名空间的输出流
    copy(thesaurus.begin(), thesaurus.end(), ostream_iterator<TEntry>(cout, "\n"));
    //为这些索引的值简历一个链表
    string keys[10];
    int i=0;
    for(TIter it=thesaurus.begin() ; it != thesaurus.end() ; ++it)
        keys[i++]=(*it).first;

    for(int count=0 ; count < 10 ; ++count)
    {
        string reply=menu(thesaurus);   //调用函数显示一个单词
        vector<string>& v=thesaurus[reply]; //找到key对应的vector
        //把vector输出
        copy(v.begin(), v.end(), ostream_iterator<string>(cout, " "));
        cout<<endl;
    }

    return 0;
}

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值