或许你已经把 C++ 作为主要的编程语言用来解决 TopCoder 上的问题。这意味着你已经简单使用过了 STL,因为数组和字符串都是作为 STL 对象传递给函数。也许你已经注意到了,很多程序员写代码比你快得多,也更简洁。
或许你还不是但想成为一名 C++ 程序猿,因为这种编程语言功能很强大还有丰富的库(也许是因为在 TopCoder 的练习室里和竞赛中看到了很多非常精简的解决方案)。
无论过去如何,这篇文章都会有所帮助。在这里,我们将回顾标准模板库(Standard Template Library—STL,一个非常有用的工具,有时甚至能在算法竞赛中为你节省大量时间)的一些强大特性。
要熟悉 STL,最简单的方式就是从容器开始。
容器
无论何时需要操作大量元素,都会用到某种容器。C语言只有一种内置容器:数组。
问题不在于数组有局限性(例如,不可能在运行时确定数组大小)。相反,问题主要在于很多任务需要功能更强大的容器。
例如,我们可能需要一个或多个下列操作:
向容器添加某种字符串
从容器中移除一个字符串
确定容器中是否存在某个字符串
从容器中返回一些互不相同的元素
对容器进行循环遍历,以某种顺序获取一个附加字符串列表。
当然,我们可以在一个普通数组上实现这些功能。但是,这些琐碎的实现会非常低效。你可以创建树结构或哈希结构来快速解决问题,但是想想:这种容器的实现是取决于即将存储的元素类型吗?例如,我们要存储平面上的点而不是字符串的话,是不是要重写这个模块才能实现功能?
如果不是,那我们可以一劳永逸地为这种容器开发出接口,然后对任何数据类型都能使用。简言之,这就是 STL 容器的思想。
前言
程序要使用 STL 时,应包含(#include)适当的标准头文件。对大部分容器来说,标准头文件的名称和容器名一致,且不需扩展名。比如说,如果你要用栈(stack),只要在程序最开头添加下面这行代码:
#include<stack>
容器类型(还有算法、运算符和所有STL也一样)并不是定义在全局命名空间,而是定义在一个叫“std”的特殊命名空间里。在包含完所有头文件之后,写代码之前添加下面这一行:
using namespace std;
还有另一个很重要的事情要记住:容器类型也是模板参数。在代码中用“尖括号”(‘<’/’>’)指明模板参数。比如:
vector<int> N;
如果要进行嵌套式的构造,确保“方括号”之间不是紧挨着——留出一个空格的位置。(译者:C++11新特性支持两个尖括号之间紧挨着,不再需要加空格)
vector<vector<int> > CorrectDefinition;
vector<vector<int>> WrongDefinition;
//Wrong: compiler may be confused by 'operator >>'
一、Vector
最简单的 STL 容器就是 vector。Vector 只是一个拥有扩展功能的数组。顺便说一下,vector 是唯一向后兼容 C 代码的容器——这意味着 vector 实际上就是数组,只是拥有一些额外特性。
vector<int> v(10); //把’v’声明成一个存放了 10 个 vector<int> 类型元素的数组,初始化为空
//实际上,当你敲下vector<int> v就创建了一个空 vector
for(int i = 0; i < 10; i++) {
v[i] = (i+1)*(i+1);
cout<<v[i]<<endl;
}
for(int i = 9; i > 0; i--) {
v[i] -= v[i-1];
}
Vector 最常使用的特性就是获取容器大小
int elements_count = v.size()
有两点要注意:首先,size() 函数返回的值是无符号的,这点有时会引起一些问题。其次,如果你想知道容器是否为空,把 vector 的 size() 返回值和0比较不是一个好的做法。你最好使用 empty() 函数.
另一个 vector 中经常使用的函数是 push_back。push_back 函数向 vector 尾部添加一个元素,容器长度加 1。思考下面这个例子:
vector<int> v1;
for(int i=1; i<10; i *= 2)
{
v1.push_back(i);//添加
}
for(unsigned int i=0; i<v1.size(); i++)
{
cout<<v1[i]<<endl;//输出
}
别担心内存分配问题——vector 不会一次只分配一个元素的空间。相反,每次用 push_back 添加新元素时,vector 分配的内存空间总是比它实际需要的更多。你应该担心的唯一一件事情是内存使用情况。
当你需要重新改变 vector 的大小时,使用 resize() 函数:
vector<int> v(20);
for(int i=0; i<20; i++)
{
v[i]=i+1;
}
v.resize(25);//重新改变 vector 的大小
for(int i=20; i<25; i++)
{
v[i] = i*2;
}
Resize() 函数让 vector 只存储所需个数的元素。如果你需要的元素个数少于 vector 当前存储的个数,剩余那些元素就会被删除。如果你要求 vector 变大,使用这个函数也会扩大它的长度,并用 0 填充新创建的元素。
注意,如果在使用了 resize() 后又用了 push_back(),那新添加的元素就会位于新分配内存的后面,而不是被放入新分配的内存当中。上面的例子得到的 vector 大小是25,如果在第二个循环中使用 push_back(),那vector 的大小最后会是30。
int main()
{
vector<int> v(20);
for(int i=0; i<20; i++)
{
v[i]=i+1;
}
v.resize(25); /
for(int i=20; i<25; i++)
{
v.push_back(i);/
}
for(unsigned int i=0; i<v.size(); i++)
{
cout<<v[i]<<" ";
}
cout<<endl<<v.size()<<endl;
return 0;
}
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 0 0 0 0 0 20 21 22 23 24
30 //大小变为30
使用 clear() 函数来清空 vector。这个函数使 vector 包含 0 个元素。它并不是让所有元素的值为0——注意——它是完全删除所有元素,成为空容器。
向 vector 添加数据的最简单方式是使用 push_back()。但是,万一我们想在除了尾部以外的地方添加数据呢?Insert() 函数可以实现这个目的。同时还有 erase() 函数来删除元素。但我们得先讲讲迭代器。
你还应该记住另一个非常重要的事情:当 vector 作为参数传给某个函数时,实际上是复制了这个 vector(也就是值传递)。在不需要这么做的时候创建新的 vector 可能会消耗大量时间和内存。实际上,很难找到一个任务需要在传递 vector 为参数时对其进行复制。因此,永远不要这么写:
void some_function(vector<int> v) //
{
//Never do it unless you’re sure what you do!
}
相反,使用下面的构造方法(引用传递):
void some_function(const vector<int> &v) //!!!!!!
{
//OK//...
}
如果在函数里要改变 vector 中的元素值,那就去掉‘const’修饰符。
int modify_vector(vector<int> &v)
{ //Correct
V[0]++;
}