STL容器(STL container)
容器的共通能力和操作
STL容器的共通能力,三个最核心的能力:
1.所有容器提供的都是值(value)而不是引用(reference),STL容器的每一个元素都必须能够被拷贝。
2.每个容器都提供可返回迭代器的函数,运用那些迭代器你就可以巡访元素。这是STL演算法赖以生存在关键介面。
3.一般而言,各项操作并非绝对安全。调用者必须确保传给操作函数的参数符合需求。违反这些需求会导致未定义的行为。
以下操作为所有容器共有,它们均满足上述核心能力。
初始化(initialization)
每个容器类型都提供了一个默认(default)构造函数,一个copy构造函数和一个析构函数:
l 以某个array的元素为初值,完成初始化动作:
int array[] = {2,3,17,33,45,77};
std::set
l 以标准输入流完成初始化动作:
std::deque
(std::istream_iterator
注意:不要漏了括号,否则你会得到一堆奇怪的警告或错误。
如果不加括号c被视为一个函数,返回值是deque
Vectors
vector模塑出一个dynamic array。不过C++ Standard并未要求不许以dynamic array实现vector,只是规定了相应条件和操作复杂度。
使用vector之前,必须包含头文件
其中类型vector是一个定义于namespace std内的template:
namespace std {
template < class T, class Allocator = allocator
class vector;
}
Vectors的能力
vectors将其元素赋值到内部的dynamic array中。元素之间总是存在某种顺序,所以vectors是一种有序群集(ordered collection)。vector支持随机存取,所以对任何一个STL演算法都可以奏效。
大小(Size)和容量(Capacity)
vector之中用于操作大小的函数有size(),empty(),max_size()。另一个与大小有关的函数是capacity(),返回vector实际能够容纳的元素数量。如果超出这个数量,vector就有必要重新配置内部内存。
vector的容量之所以很重要,有以下两个原因:
1. 一旦内存重新配置,和vector元素相关的引用(references)、指针(pointers)、迭代器(iterators)都会失效。
2. 内存重新配置很耗时间。
如果执行速度对你而言至关重要,那么就必须考虑容量问题。
你可以使用reserve()保留适当容量,避免一再重新配置内存:
std::vector
v.reserve(80);
另一种避免重新配置内存的方式是,初始化期间就向构造函数传递附加参数,构造出足够的空间。如果你的参数一个数值,它将成为vector的起始大小。
std::vector
要获得这种能力,此种元素必须提供一个默认的构造函数。如果你这么做只不过是为了保留足够的内存,那倒不如使用reserve()。
vector的容量,概念上和strings类似,不过有一个大不同点:vector不能使用reserve()来缩减容量,这一点和strings不同。在许多实现方案中即使你不调用reserve(),当你第一次插入元素时也会一口气分配整块内存(例如2K)。
这里有一个间接缩减vector容量的小窍门。两个vectors交换内容后,两者的容量也会互换:
template
void shrinkCapacity(std::vector
std::vector
v.swap(tmp);
}
你甚至可以利用下面的语句直接缩减容量:
std::vector
注意:swap()之后原先所有的references、pointers、iterators都失效。
Vector的操作函数
操作 |
效果 |
vector |
产生一个空的vector,其中没有任何元素 |
vector |
产生另一个同型vector的副本 |
vector |
利用元素的默认构造函数生成大小为n的vector |
vector |
产生一个大小为n每个元素都是elem的vector |
vector |
产生一个vector,以区间[beg,end)作为元素初值 |
c.~vector |
销毁所有元素,并释放内存 |
元素存取(Element Access)
按照C和C++的惯例,第一元素的索引为0,最后元素的索引为size()-1所以第n个元素的索引是n-1。对于non-const vectors,这些函数都返回元素的引用(reference)。也就是说你可以使用这些操作函数来更改元素内容。
操作 |
效果 |
c.at(idx) |
返回索引idx的元素,如果idx越界,抛出out_of_range |
c[idx] |
返回索引idx的元素。不进行范围检查 |
c.front() |
返回第一元素,不检查第一个元素是否存在 |
c.back() |
返回最后一个元素,不检查第一个元素是否存在 |
迭代器相关函数(Iterator Functions)
操作 |
效果 |
c.begin() |
返回一个随机存取迭代器,指向第一个元素 |
c.end() |
返回一个随机存取迭代器,指向最后元素的下一位置 |
c.rbegin() |
返回一个逆向迭代器,指向逆向迭代器的第一元素。 |
c.rend() |
返回一个逆向迭代器,指向逆向迭代器的最后元素下一位置。 |
vector迭代器持续有效,除非发生两种情况:(1)使用者在一个较小索引位置上插入或移除元素,(2)由于容器变化引起内存重新分配。
插入(insert)和移除(remove)元素
操作 |
效果 |
c.insert(pos,elem) |
在pos位置上插入一个elem副本,并返回新元素位置 |
c.insert(pos,n,elem) |
在pos位置上插入n个elem副本。无返回值 |
c.insert(pos,beg,end) |
在pos位置上插入区间[beg,end)内的所有元素的副本。无返回值。 |
c.push_back(elem) |
在尾部添加一个elem副本。 |
c.pop_back() |
移除最后一个元素(但不返回)。 |
c.erase(pos) |
移除pos位置上的元素,返回下一元素的位置。 |
c.erase(beg,end) |
移除[beg,end)区间内所有元素,返回下一元素位置。 |
c.resize(num) |
将元素数量改为num(如果size()变大了,多出来的新元素都需以默认构造函数完成) |
c.resize(num,elem) |
将元素数量改为num(如果size()变大了,多出来的新元素都是elem的副本) |
c.clear() |
移除所有元素,将容器清空。 |
vector并未提供任何函数可以直接移除与某值相等的所有元素。这是演算法发挥威力的时候:
std::vector
coll.erase( remove(coll.begin(),coll.end(),val ), coll.end() );
如果只是要移除与某值相等的第一个元素,可以这么做:
std::vector
std::vector
pos = find( coll.begin(), coll.end(), val );
if( pos != coll.end() ) {
coll.erase(pos);
}
将Vectors当作一般Arrays使用
对于vector v中任意一个合法索引i,一下算式肯定为true:
&v[i] == &v[0] + i
保证了这一点,就可推到出一系列重要结果。简单地说,任何地点只要你需要一个dynamic array,你就可以使用vector。例如你可以利用vector来存放常规的C字符串(类型为char*或const char*):
std::vector
v.resize(41);
strcpy(&v[0],"hello, world");
printf("%s/n", &v[0]);
不过,这么运用vector你可得小心(和使用dynamic array一样小心),例如你必须确保vector大小足够,如果你用的是C-String,记住最后有个'/0'元素。
注意:千万不要把迭代器当作第一元素的位置来传递。vector迭代器是由实现版本定义的,也许并不是个一般的指针。
printf("%s/n",v.begin());
printf("%s/n",&v[0]);
异常处理(Exception Handling)
vector只支持最低限度的逻辑错误检查。at()是唯一被标准规格要求可能抛出异常的一个函数。只有一般标准异常(例bad_alloc),或用户自定操作函数的异常,才可能发生。
如果vector调用的函数抛出异常,C++标准程序库做出如下保证:
1.如果push_back()插入元素时发生异常,搞函数不生效用。
2.如果元素的拷贝动作(包括copy构造函数和assignment运算符)不抛出异常,那么insert()要么成功,要么不生效用。
3.pop_back()绝不会抛出任何异常。
4.如果元素拷贝动作不抛出异常,erase()和clear()就不抛出异常。
5.swap()不抛出异常。
6.如果元素拷贝动作绝对不会抛出异常,那么所有操作不是成功,就是不生效用。这类元素可被称为POD(plain old data,简朴的老式数据)。POD泛指哪些无C++特熊的类型,例如C structure便是。
所有这些保证都基于一个条件:析构函数不得抛出异常。
Vectors运用实例:
#include
#include
#include
#include
using namespace std;
int main()
{
vector
sentence.reserve(5);
sentence.push_back("Hello,");
sentence.push_back("how");
sentence.push_back("are");
sentence.push_back("you");
sentence.push_back("?");
copy( sentence.begin(), sentence.end(),
ostream_iterator
cout << endl;
cout << " max_size(): " << sentence.max_size() << endl;
cout << " size(): " << sentence.size() << endl;
cout << " capacity(): " << sentence.capacity() << endl;
swap( sentence[1], sentence[3] );
sentence.insert( find(sentence.begin(),sentence.end(), "?"),
"always" );
sentence.back() = "!";
copy( sentence.begin(), sentence.end(),
ostream_iterator
cout << endl;
cout << " max_size(): " << sentence.max_size() << endl;
cout << " size(): " << sentence.size() << endl;
cout << " capacity(): " << sentence.capacity() << endl;
}
Class Vector
C++标准程序库专门针对元素类型为bool的vector设计了一个特殊版本,目的是获取一个优化的vector。其耗用空间远远小于以一般vector实现作出来的bool vector。特殊版本之用一个bit来存储一个元素。C++的最小可定址值通常以byte为单位。所以需针对references和iterators做特殊考虑。
操作 |
效果 |
c.flip() |
将所有bool元素值取反值,亦即求誧数 |
m[idx].flip() |
将索引idx的bit元素取反值 |
m[idx] = val |
令索引idx的bit元素值为val |
m[idx1] = m[idx2] |
令索引idx1的值为索引idx2值 |