分类:
2010-10-25 18:20:36
STL容器包含元素的要求
STL容器的元素必须可以拷贝构造(copy-constructible)和可赋值( assignable)。本质上来讲,可拷贝构造意味着对象可以拷贝给另一个对象(尽管标准的解释要复杂的多)。同样的,可赋值意味着可以可以将一个对象赋值给另一个对象。这些定义可能听起来是多余的,因为对象一般都是可拷贝构造和可赋值的。
另一个附加的要求是容器元素必须有拷贝构造器、默认构造器、赋值运算符和公有申明的销毁器(显式或隐含的)。
重载运算符[]和成员函数at()使直接访问vector的元素成为可能。因为同时有const和非const
版本,所以他们可分别用于访问const元素和非const元素。
重载的[]运算符设计的和内建版本一样有效率。因此,也不会检查是否引用的是有效的元素。由于缺乏运行期检查保证了最快的访问速度(运算符[]的调用通常是内联的)。但是非法的下标的运算符[]是未定义行为。当性能十分重要,当代码写的很仔细不可能有非法下标时,使用[]运算符。[]符号也更易读更直观。尽管如此,运行期检查在有些情况下也是不可避免的,例如当下标值是从外部源得到的:函数、数据库记录、或输入的。在这种情况下你要使用成员函数at ()来代替运算符[]。at()执行范围检查,并且在试图访问范围以外成员时它抛出类型std::out_of_range
的异常。这里时一个例子:
#include
#include
#include
#include
using namespace std;
int main()
{
vector
vs.push_back("string"); //增加第一个元素
vs[0] = "overriding string"; //重载运算符[]
try
{
cout<< vs.at(10) <
catch(std::out_of_range & except)
{
//下标越界的处理程序
}
}//end main
Front 和back操作分别引用容器开始和结尾。成员函数push_back()在容器的结尾附加一个元素。当容器耗尽它的空闲内存时,函数重分配内存再附加元素。成员函数pop_back()将最后一个元素移出容器。成员函数front()和back()分别访问容器开始和结尾的一个元素。front()和 back()有const和非const版本。例如
#include
#include
using namespace std;
int main()
{
vector
v.push_back(5);
v.push_back(10);
cout<<"front: " << v.front() << endl; //5
cout<<"back: " << v.back() << endl; //10
v.pop_back(); //remove v[1]
cout<<"back: " << v.back() << endl; //now 5
return 0;
}
STL容器重载赋值运算符,因此允许同类型的容器互相赋值,例如
#include
#include
using namespace std;
int main()
{
vector
vi.push_back(1);
vi.push_back(2);
vector
//拷贝vi的内容到new_vector,自动增长到合适大小
new_vector = vi;
cout << new_vector[0] << new_vector[1] << endl; //显显示1和2
return 0;
}
每一个vector元素丢必须是同样的大小。因为派生对象可能有额外的成员,它的大小可能比基类要大。不要将派生对象存储在vector<基类 >中,因为这将导致未定义的向上对象转换(object slicing)。但是你可以通过存储派生对象指针到vector<基类*>来达到多态。
在队列数据模型中(一个队列也叫FIFO先进先出),插入的第一个元素在顶部,其后插入的元素都在其后。队列的两个基本操作是pop()和push()。push()操作将一个元素插入到队列的底部。pop()操作从顶端移出第一次
插入的元素;从而使第二个元素成为顶端元素。STLqueue容器象下面这样使用:
#include
#include
using namespace std;
int main()
{
queue
iq.push(93); //插入第一个元素,它是最顶部的元素
iq.push(250);
iq.push(10); //最后一个插入的元素在底部
cout<<"currently there are "<< iq.size() << " elements" << endl;
while (!iq.empty() )
{
cout <<"the last element is: "<< iq.front() << endl; //front()返回
//顶部元素
iq.pop(); //移出顶部元素
}
return 0;
}
STL也定义了双向队列,或deque(pronounced "deck")容器。deque是能有效处理尾部的队列。其他类型的队列有priority_queue。priority_queue的所有元素以其优先权排序。有高有限权的元素在顶部。为了取得作为priority_queue元素的资格,对象必须定义<运算符(priority_queue在“函数对象”一节讨论)。
Iterators可以认为是一种通用指针。他们用于操作容器的元素而不需要知道实际元素类型。许多成员函数,比如begin()和end(),返回一个指向容器尾的迭代器。
所有的STL容器提供begin()和end()成员函数。begin()返回指向容器第一个元素的迭代器。例
如
#include
#include
#include
using namespace std;
int main()
{
vector
v[0] = 10;
vector
*p = 11; //通过p将一个新的值赋给v[0]
cout << *p; //输出11
return 0;
}
另一方面成员函数end()返回一个指向位于容器最后一个元素之后(past)的迭代器。第一次听到时很不可思议,但是如果你考虑到C风格的字符串是如何表示的,这就不是什么不可理解的事:一个附加的null字符被自动附加到char 数组最后一个元素的后面。在STL中附加元素起同样的作用,标记容器的结尾。让end()返回指向容器最后一个元素之后的迭代器对于for和while循环很有用。例如
vector
int n=0;
for (vector
begin()和end()有两种版本:const和非const。非const版本返回非const 迭代器,它允许用户修改容器元素的值。const版本返回const 迭代器,它不允许用户修改容器。
例如
const vector
vector
vector
*cp = 'a'; //error,试图改变const对象
cout << *cp; //OK
成员函数rbegin()和rend()(降序begin()和降序end())与begin()和end()十分相似,只是返回降序迭代器,它们适用于降序序列。本质上说,降序迭代器就是普通迭代器,只不过所重载的运算符++不同--。需要降序访问容器的时候,是很有用的。
例如
#include
#include
#include
using namespace std;
void ascending_order()
{
vector
double d = 0.1;
for (vector
*p = d;
d+= 0.1;
}
//以升序显式v的元素
for (vector
{
cout<< *rp<
}
与begin()和end()一样rbegin()和rend()也有const和非const
版本
STL 的大多数实现使用指针来实现迭代器。但是一个迭代器不需要一个指针,并且有一个很好的理由。考虑一个存储在6GB磁盘上扫描图象的巨大vector;大多数机器上内建的指针只有32位不够来表示这种巨大的vector。代替指针的一种方案是用64位整数来实现迭代器。同样的,保存位或半元组(nibbles )(内建指针不能引用)也可以通过不同的底层类型的迭代器来来实现,并提供相同的接口然而,有时候纯指针也用于迭代特定实现的容器的元素;例如
#include
#include
using namespace std;
void hack()
{
vector
vi.push_back(5);
int *p = vi.begin();//糟糕的程序设计习惯,尽管它可以工作
*p = 6; //赋值vi[0]
cout<
使用纯指针来代替迭代器是一种糟糕的程序设计习惯,应该避免。
当元素可以被访问但不能改变时,使用容器的const迭代器。作为普通指针类型,使用非const迭代器暗示容器的内容是可改变的。const使编译器可以检测到简单的错误,也更可读。
就像前面说的,内建数组是一种有效的序列容器。因此,可以通过数组首尾的地址可以用内建数组的内容来初始化一个vector。例如
#include
#include
using namespace std;
int main()
{
int arr[3];
arr[0] = 4; arr[1] = 8; arr[2] = 16;
vector
&arr[3] ); //必须指向数组最后一个元素之后的元素
cout<< vi[0] << '\t' << vi[1] << '\t' << vi[2] <
}
当成员函数改变它的容器时将发生重分配。可能改变的成员函数是reserve()和resize(),push_back()和pop_back(), erase(),clear(),insert()和其他。另外,赋值运算符和可能改变的算法也可能导致重分配。当容器重分配它的元素是,他们的地址改变了。因此,存在的迭代器的值无效了。
例如
#include
#include
using namespace std;
int main()
{
list
payroll.push_back(5000.00);
list
for (int i = 0 ; i < 10; i++)
{
payroll.push_back(4500.00); //向payroll插入10以上元素;
//将产生重分配
}
//危险
cout << "first element in payroll: "<< *p <
}
在前面的例子中,payroll可能在插入十个元素的过程中发生重分配,因此p的值可能失效。使用失效的迭代器就像使用指向已删除对象的指针一样,都是为定义行为。为了安全起见,推荐你在调用了可能改变的函数之后因该重赋值迭代器。例如
list
for (int i = 0 ; i < 10; i++)
{
payroll.push_back(4500.00); //可能发生重分配
}
p = payroll.begin(); //重赋值p
cout <<"first element in payroll: "<<*p<
作为选择,你可以通过在实例化预分配足够的内存来避免重分配。例如
int main()
{
list
payroll.reserve(11);
payroll.push_back(5000.00);
list
for (int i = 0 ; i < 10; i++)
{
payroll.push_back(4500.00); //不会发生重分配
}
cout << "first element in payroll: "<< *p <
}