STL实践与分析
--顺序容器的定义、迭代器
一、顺序容器的定义
顺序容器 |
|
容器适配器 |
|
vector |
支持快速随机访问 |
stack |
后进先出(LIFO) |
list |
支持快速插入/删除 |
queue |
先进先出(FIFO) |
deque |
双端队列 |
priority_queue |
有优先级管理的队列 |
1、容器类型的操作集合形成了以下层次结构:
1)一些操作适用于所有容器类型;
2)另外一些操作只适用于顺序或关联容器;
3)还有一些操作只适用于顺序或关联容器类型的一个子集。
2、容器构造函数
C<T>c; |
创建一个名为c的空容器。C是容器类型名,如vector,T是元素类型,如int或string适用于所有容器 |
Cc(c2); |
创建容器c2的副本c;c和c2必须具有相同的容器类型,并存放相同类型的元素。适用于所有容器。 |
Cc(b,e); |
创建c,其元素是迭代器b和e标示的范围内元素的副本。 适用于所有容器 |
Cc(n, t); |
用n个值为t的元素创建容器c,其中值t必须是容器类型C的元素类型的值,或者是可转换为该类型的值。 只适用于顺序容器 |
Cc(n); |
创建有n个值初始化元素的容器c 只适用于顺序容器 |
3、将一个容器初始化为另一个容器的副本
将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须完全相同。
[cpp] view plain copy print ?
- vector<int> ivec;
- vector<int> ivec2(ivec);
- vector<double> dvec(ivec);
- list<int> ilist(ivec);
vector<int> ivec;
vector<int> ivec2(ivec); //OK
vector<double> dvec(ivec); //Error
list<int> ilist(ivec); //Error
4、初始化为一段元素的副本
系统允许通过一对迭代器间接实现不同种容器之间进行复制:使用迭代器时,不要求容器类型相同,容器内元素类型也可以不相同,只要他们相互兼容,能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。
[cpp] view plain copy print ?
- vector<string> svec;
-
- list<string> slist(svec.begin(),svec.end());
-
- vector<string>::iterator mid = svec.begin() + svec.size()/2;
- deque<string> front(svec.begin(),mid);
- deque<string> back(mid,svec.end());
-
- char *word[] = {"stately","plump","buck","mulligan"};
- list<string> slist2(word,word + sizeof(word)/sizeof(*word));
-
- vector<int> ivec;
-
- vector<double> dvec(ivec.begin(),ivec.end());
vector<string> svec;
//...
list<string> slist(svec.begin(),svec.end()); //OK
vector<string>::iterator mid = svec.begin() + svec.size()/2;
deque<string> front(svec.begin(),mid); //OK
deque<string> back(mid,svec.end()); //OK
char *word[] = {"stately","plump","buck","mulligan"};
list<string> slist2(word,word + sizeof(word)/sizeof(*word)); //OK
vector<int> ivec;
//...
vector<double> dvec(ivec.begin(),ivec.end()); //OK
5、分配和初始化指定数目的元素
不提供元素初始化式时,标准库将为该容器实现值初始化,采用这种类型的初始化,元素类型必须是内置或复合类型,或者是提供了默认构造函数的类类型。如果元素类型没有默认构造函数,则必须显式的指定其元素初始化式。
接受容器大小做形参的构造函数只适用于顺序容器,而关联容器不支持这种初始化。
[cpp] view plain copy print ?
- const list<int>::size_type list_size = 64;
- list<int> ilist(list_size);
- list<string> slist(list_size);
-
- list<string> strList(list_size,"Ha~");
const list<int>::size_type list_size = 64;
list<int> ilist(list_size); //OK
list<string> slist(list_size); //OK
list<string> strList(list_size,"Ha~"); //OK
[cpp] view plain copy print ?
-
- vector<string> strVec1;
- vector<string> strVec2(strVec1);
- vector<string> strVec3(strVec2.begin(),strVec2.end());
- vector<string> strVec4(strVec3.size());
- vector<string> strVec5(strVec4.size(),"ANS");
//P267 习题9.2
vector<string> strVec1;
vector<string> strVec2(strVec1);
vector<string> strVec3(strVec2.begin(),strVec2.end());
vector<string> strVec4(strVec3.size());
vector<string> strVec5(strVec4.size(),"ANS");
6、容器内元素的约束
C++语言中,大多数类型都可用作容器的元素类型。容器元素类型必须满足
以下两个约束:
•元素类型必须支持赋值运算。
•元素类型的对象必须可以复制。
容器操作的特殊要求
支持复制和赋值功能是容器元素类型的最低要求。此外,一些容器操作对元素类型还有特殊要求。如果元素类型不支持这些特殊要求,则相关的容器操作就不能执行:我们可以定义该类型的容器,但不能使用某些特定的操作。
[cpp] view plain copy print ?
- class Foo
- {
- public:
- Foo(int x)
- {
-
- }
- };
-
- int main()
- {
- vector<Foo> empty;
- vector<Foo> bad(10);
- vector<Foo> ok(10,1);
- }
class Foo
{
public:
Foo(int x)
{
}
};
int main()
{
vector<Foo> empty; //OK
vector<Foo> bad(10); //Error
vector<Foo> ok(10,1); //OK
}
有在同时指定每个元素的初始化式时,才能使用给定容器大小的构造函数来创建同类型的容器对象。
7、容器的容器
因为容器受容器类型的约束,所以可定义元素是容器的容器:
[cpp] view plain copy print ?
- vector< vector<string> > vvec;
vector< vector<string> > vvec;
【注意:】
在指定容器元素为容器类型时,必须如下使用空格:
[cpp] view plain copy print ?
- vector< vector<string> > vvec;
- vector< vector<string>> bad_vvec;
vector< vector<string> > vvec; //OK
vector< vector<string>> bad_vvec; //Error
[cpp] view plain copy print ?
-
- list< deque<int> > dList;
//P268 习题9.4
list< deque<int> > dList;
[cpp] view plain copy print ?
-
- class Foo
- {
- public:
- Foo(int x)
- {
-
- }
- };
-
- int main()
- {
- list<Foo> FList(10,1);
- }
//习题9.6
class Foo
{
public:
Foo(int x)
{
}
};
int main()
{
list<Foo> FList(10,1);
}
二、迭代器和迭代器范围
所有标准库都提供的迭代器运算 |
*iter |
返回迭代器iter所指向的元素的引用 |
iter-> mem |
对iter进行解引用,获取指定元素中名为mem的成员。等效于(*iter).mem |
++iter/iter++ |
给iter加1,使其指向容器里的下一个元素 |
--iter/iter-- |
给iter减1,使其指向容器里的前一个元素 |
iter1== iter2 iter1!= iter2 |
比较两个迭代器是否相等(或不等)。当两个迭代器指向同一个iter2容器中的同一个元素,或者当它们都指向同一个容器的超出末端iter1!=的下一位置时,两个迭代器相等. |
vector和deque类型迭代器支持的操作 |
iter+ n iter- n |
在迭代器上加(减)整数值n,将产生指向容器中前面(后面)第n个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一位置 |
iter1+= iter2 iter1-= iter2 |
这里迭代器加减法的复合赋值运算:将iter1加上或减去iter2的运算结果赋给iter1 |
iter1- iter2 |
两个迭代器的减法,其运算结果加上右边的迭代器即得左边的迭代器。这两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置 只适用于vector和deque容器 |
>,>=,<,<= |
迭代器的关系操作符。当一个迭代器指向的元素在容器中位于另一个迭代器指向的元素之前,则前一个迭代器小于后一个迭代器。关系操作符的两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置 只适用于vector和deque容器 |
关系操作符只适用于vector和deque容器,因为只有这两种容器为其元素提供快速、随机的访问。
[cpp] view plain copy print ?
- vector<int> ivec;
-
- vector<int>::iterator mid = ivec.begin() + ivec.size()/2;
-
- list<int> ilist(ivec.begin(),ivec.end());
- list<int>::iterator iter = ilist.begin() + ilist.size()/2;
- list<int>::iterator iter1 = ilist.begin(),
- iter2 = ilist.end();
- if (iter1 < iter2)
- {
-
- }
vector<int> ivec;
//...
vector<int>::iterator mid = ivec.begin() + ivec.size()/2; //OK
list<int> ilist(ivec.begin(),ivec.end());
list<int>::iterator iter = ilist.begin() + ilist.size()/2; //Error
list<int>::iterator iter1 = ilist.begin(),
iter2 = ilist.end();
if (iter1 < iter2) //Error
{
//...
}
list容器的迭代器既不支持算术运算符,也不支持关系运算符,它只是提供前置/后置的自增、自减运算以及相等/不等运算。
[cpp] view plain copy print ?
-
- int main()
- {
- list<int> iList;
- for (size_t i = 0; i != 15; ++i)
- {
- iList.push_back(i+1);
- }
- for (list<int>::iterator iter = iList.end(); iter != iList.begin();)
- {
- cout << *(--iter) << endl;
- }
- }
//P270 习题9.9
int main()
{
list<int> iList;
for (size_t i = 0; i != 15; ++i)
{
iList.push_back(i+1);
}
for (list<int>::iterator iter = iList.end(); iter != iList.begin();)
{
cout << *(--iter) << endl;
}
}
1、对形成迭代器范围的迭代器的要求
迭代器 first和last如果满足以下条件,则可形成一个迭代器范围:
•它们指向同一个容器中的元素或超出末端的下一位置。
•如果这两个迭代器不相等,则对first反复做自增运算必须能够到达last。换句话说,在容器中,last绝对不能位于first之 前。【P270,编译器自己也不能保证上述要求】
2、使用左闭右开区间的意义
1)当first与last相等,迭代器范围为空
2)当first与last不相等时,迭代器范围内至少有一个元素:size= last – fist
[cpp] view plain copy print ?
-
- bool findVal(vector<int>::const_iterator beg,vector<int>::const_iterator end,int val)
- {
- while (beg != end)
- {
- if (*beg == val)
- {
- return true;
- }
- ++ beg;
- }
- return false;
- }
-
- int main()
- {
- vector<int> ivec;
- for (int i = 0;i != 20; ++i)
- {
- ivec.push_back(i);
- }
- vector<int>::const_iterator mid = ivec.begin() + ivec.size()/2;
- cout << findVal(ivec.begin(),ivec.end(),10) << endl;
- cout << findVal(ivec.begin(),mid,10) << endl;
- }
//P271 习题9.12
bool findVal(vector<int>::const_iterator beg,vector<int>::const_iterator end,int val)
{
while (beg != end)
{
if (*beg == val)
{
return true;
}
++ beg;
}
return false;
}
int main()
{
vector<int> ivec;
for (int i = 0;i != 20; ++i)
{
ivec.push_back(i);
}
vector<int>::const_iterator mid = ivec.begin() + ivec.size()/2;
cout << findVal(ivec.begin(),ivec.end(),10) << endl;
cout << findVal(ivec.begin(),mid,10) << endl;
}
[cpp] view plain copy print ?
-
- vector<int>::const_iterator findVal(vector<int>::const_iterator beg,
- vector<int>::const_iterator end,
- int val)
- {
- while (beg != end)
- {
- if (*beg == val)
- {
- return beg;
- }
- ++ beg;
- }
- return beg;
- }
//习题9.13
vector<int>::const_iterator findVal(vector<int>::const_iterator beg,
vector<int>::const_iterator end,
int val)
{
while (beg != end)
{
if (*beg == val)
{
return beg;
}
++ beg;
}
return beg;
}
[cpp] view plain copy print ?
-
- int main()
- {
-
- vector<string> strVec;
- string val;
- while (cin >> val)
- {
- strVec.push_back(val);
- }
- for (vector<string>::iterator iter = strVec.begin(); iter != strVec.end(); ++iter)
- {
- cout << *iter << endl;
- }
- }
//习题9.14
int main()
{
// freopen("input","r",stdin);
vector<string> strVec;
string val;
while (cin >> val)
{
strVec.push_back(val);
}
for (vector<string>::iterator iter = strVec.begin(); iter != strVec.end(); ++iter)
{
cout << *iter << endl;
}
}
[cpp] view plain copy print ?
-
- int main()
- {
-
- list<string> strVec;
- string val;
- while (cin >> val)
- {
- strVec.push_back(val);
- }
-
- for (list<string>::iterator iter = strVec.begin(); iter != strVec.end(); ++iter)
- {
- cout << *iter << endl;
- }
- }
//习题9.15
int main()
{
// freopen("input","r",stdin);
list<string> strVec; //修改vector -> list
string val;
while (cin >> val)
{
strVec.push_back(val);
}
//修改vector -> list
for (list<string>::iterator iter = strVec.begin(); iter != strVec.end(); ++iter)
{
cout << *iter << endl;
}
}
3、使迭代器失效的容器操作
一些容器的操作会修改容器的内在状态或移动容器内的元素。这样的操作使所有指向被移动的元素的迭代器失效,也可能同时使其他迭代器失效。使用无效迭代器是没有定义的,可能会导致与悬垂指针相同的问题。使用无效迭代器将会导致严重的运行时错误。
无法检查迭代器是否有效,也无法通过测试来发现迭代器是否已经失效。任何无效迭代器的使用都可能导致运行时错误,但程序不一定会崩溃,否则检查这种错误也许会容易些o(∩∩)o...。
【建议:】
使用迭代器时,通常可以编写程序使得要求迭代器有效的代码范围相对较短。 然后,在该范围内,严格检查每一条语句,判断是否有元素添加或删除,从而相应地调整迭代器的值。