第七章 通用容器
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()
功能:用指定函数对象产生的值去给容器指定范围内元素赋值
- template<class ForwardIterator, class Generator>
- void generate(
- ForwardIterator _First,
- ForwardIterator _Last,
- Generator _Gen
- );
generate_n()
功能:一个函数对象产生的值给一定的范围内指定数目的容器元素赋值
- template<class OutputIterator, class Diff, class Generator>
- void generate_n(
- OutputIterator _First,
- Diff _Count,
- Generator _Gen
- );
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;
}