STL 基础及原理

STL教程概述

也许你在解决 TopCoder 上的问题时已经用过 C++,可能你会发现别人在实现算法的时候,代码很简洁干练。又或许你是一个初级 C++ 程序员,那么你就更应该好好读一下这篇stl教程了。在本教程中,我会逐步向你介绍 C++ STL(Standard Template Library)的强大特性,这样你在各种 OJ 上挑战的时候或者在工作中,在算法实现上能节省很多时间。

1. stl教程之Containers

要学好 STL,首先要从 Containers 开始。当你要处理一系列的元素的时候,必然离不开某种特定的 container,在原生的 C 语言中,只有一种 container ——数组(array)。但是 array 只有基本的功能,而我们往往需要功能很强大的 container。比如以下功能:
1)往 container 中添加 string;
2)删除 container 中的 string;
3)查询 container 中是否有一个 string;
4)返回 container 中不同元素的个数;
5)遍历整个 container 获取里面的所有 string 列表。
当然你可以利用 array 自己来实现所有这些功能,但是这就要你花很多时间处理很多繁琐的细节。所以还是明智点,直接用 STL 吧。
在使用 STL 时,要先 #include 适当的头文件。例如你要用 stack,那么就应该在程序开头加上这句:

#include <stack>

STL 的各 Container 以及 algorithms 并定义在命令空间“std”中,并不是定义在全局的命名空间中,所以在 include 对应头文件后,还得加上这句:
using namespace std;
还有一点要注意的是,container 的类型都是模板参数类型,模板参数放在 <>(尖括号中),如定义int类型的 vector,如下:

vector<int> v1;

在定义嵌套的 container 的时候,尖括号间要加上适当的空格:

vector< vector<int> > v1; // 正确的定义方式
vector<vector<int>> v2; // 错误的定义方式,编译器会把 >> 和输出操作的 >> 混淆

2. stl教程之Vector

STL 中最简单的 container 是 vector,vector 只是简单的扩展了一下 array 的功能,它也是唯一个能向后兼容 C 标准的 container,因为它本身就是 array,只是多了一些特性而已。

 vector<int> v(10); 
 for(int i = 0; i < 10; i++) { 
      v[i] = (i+1)*(i+1); 
 } 
 for(int i = 9; i > 0; i--) { 
      v[i] -= v[i-1]; 
 } 

vector<int> v;

在定义 v 的时候,就创建了一个空的 vector。
注意下面这行代码:

vector<int> v[10]; 

在这里,v 是一个长度为 10 的数组,每个数组元素是一个 vector,而不是 int。但是我们的本意是要定义一个 vector,其长度为 10,即存放 10 个整数的一个 vector。我们应该这样做

 vector<int> v(10); 

要用圆括号,而不是方括号。

要知道vector的长度,我们可以调用size函数:

int elements_count = v.size(); 

关于size函数,我们要注意两点:(1)size() 返回的是无符号整型,但是有时我们可能会定义一些宏,如 sz(C),这个宏用于返回 C 的长度,它返回的是 int 类型(有符号的);(2)在判断一个 container 是否为空的时候,最好不要用 v.size() 和 0 比较来判断,而是用 empty() 函数:

 bool is_nonempty_notgood = (v.size() >= 0); // 最好不要这这样写
 bool is_nonempty_ok = !v.empty(); // 应该这样写

因为并不是所有的 containers 的 size() 函数都是 O(1) 的,因为有的 container 在调用 size() 函数的时候,要做遍历操作等。

vecto r的另一个常用函数是 push_back(),该函数向 vector 尾部添加一个新的元素,并把 vector 的 size 增 1 :

 vector<int> v; 
 for(int i = 1; i < 1000000; i *= 2) { 
      v.push_back(i); 
 } 
 int elements_count = v.size(); 

不用担心内存分配问题 —— 在分配内存的时候,vector 并不是逐一申请每个元素的内存的,每次在进行 push_back 的时候,如果空间不够大,它会一次性申请多个元素的空间。
如果你要调整 vector 的大小,可以调用 resize() 函数:

 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[i] = i*2; 
 } 

resize() 函数调整出来的 vector 会包含它原先有的那些元素,但是如果你把空间调小到不足以容纳原来那些元素的程序,那么末尾的元素会被舍弃。如果 resize() 的空间大于原先的长度,那么新创建的元素会被填充 0 。

注意如果你是在调用 resize() 函数后调用 push_back(),系统还是会分配新的空间来存放添加进来的元素:

 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*2); // Writes to elements with indices [25..30), not [20..25) ! 
 }

如上我们把 v 调大到 25,然后调用 push_back() 函数添加一些元素,但是新的元素是从下标为 25 开始的位置开始存放的,而不是 20 。
在清空 vector,就调用 clear() 函数,这个函数会使得 vector 包含的元素个数变成 0,而不是把元素的值变为 0 。

初始化 vector 的方式有很多种,也可以用已有的 vector 来初始化新声明的 vector:

 vector<int> v1; 
 // ... 
 vector<int> v2 = v1; 
 vector<int> v3(v1); 

如上初始化后, v2 和 v3 是一样的。
如果你要创建指定开度的 vector,可以如下调用:

vector<int> Data(1000); 

如上定义了一个长度为 1000 的 vector,它所有元素的初值会被置为 0 。如果你不想用默认的初始值,你可以自己指定:

vector<string> names(20, “Unknown”); 

如上定义了一个长度为 20 的 vector,存放 string 类型,所有元素初值为 "Unknown" 。

还可以定义多维的 vector,最简单的二维 vector 定义如下:

vector< vector<int> > Matrix; 

我们也可以指定二维 vector 的长度:

 int N, M; 
 // ... 
 vector< vector<int> > Matrix(N, vector<int>(M, -1)); 

如上,我们定义了一个 N*M 的矩阵,所有元素初值为 -1 。

现在我们知道 push_back() 函数总是会在 vector 的末尾添加元素,但是如果我们想在指定的位置添加元素的话,那么就得调用 insert() 函数了;对应的 erase() 函数用于删掉指定的元素,我们稍后介绍完 iterators 讲到它们。

要注意一点:如果你把 vector 当成参数直接传递给一个函数,那么该函数在接收的时候,实际上会创建一个这个 vector 的拷贝,通常这会花费很多时间和空间,而实际上往往不需要这么做:

 void some_function(vector<int> v) { // 除非你确定要这样做,否则最好不要这样写 
      // ... 
 } 

我们应该这样写(传递 vector 的引用)

 void some_function(const vector<int>& v) { // OK 
      // ... 
 } 

加上 const 限定,被调用函数就不能修改 vector 的内容。如果你想让被调用函数能修改原 vector 的内容,那么应该这样写:

 int modify_vector(vector<int>& v) { // Correct 
      V[0]++; 
 } 

3. stl教程之Pairs

在讲 iterators 前,我们先来简单讲一下 pairs,pairs 在 STL 中的应用非常广泛。pairs 的基本定义如下:

 template<typename T1, typename T2> struct pair { 
      T1 first; 
      T2 second; 
 }; 

pair<int, int> 定义了一对整数,pair<string, pair<int, int> > 定义一个 string 和两个整数对,可如下访问其元素值:

 pair<string, pair<int,int> > P; 
 string s = P.first; // 指前面的 string 
 int x = P.second.first; // 指后面的第一个 int 
 int y = P.second.second; // 指后面的第二个 int 

pairs 最大的作用是它内部有一个比较函数,可用于元素间的比较。如果两个 pairs 的第一个元素不相等,那么它们的大小由第一个元素的比较决定;否则由第二个元素的比较决定。元素类型为 pairs 的 array 或 vector,可以非常方便的调用 STL 内部的排序函数来排序。
pairs 在关联容器(associative containers)中的使用很广泛,稍后我们会讲到。

4. stl教程之Iterators

在访问 containers 中的数据的时候,就得用到 Iterators 了。如下示例是一个反转数组元素的程序,我们先用类 C 的方式来实现(第一种方法):

 void reverse_array_simple(int *A, int N) { 
      int first = 0, last = N-1;
      While(first < last) {
           swap(A[first], A[last]); // swap(a,b) is the STL function 
           first++; // Move first index forward 
           last--; // Move last index back 
      } 
 } 

我们还可以用指针来写(第二种方法):

 void reverse_array(int *A, int N) { 
      int *first = A, *last = A+N-1; 
      while(first < last) { 
           Swap(*first, *last); 
           first++; 
           last--; 
      } 
 }

然后我们再来考虑一个问题:如何对一个双向链表进行反转操作呢?显然按上面的第一种方法是不可行的,因为它基于元素下标索引。而在双向链表中, 我们没法在 O(1) 的时间内取得所有结点的下标。但是第二种方法是可行的,因为它通过指针来操作元素:获取元素值、比较元素大小、指针自增、指针自减,Iterators 就实现这些基本功能。所有的 STL container 都可以通过 iterator 遍历。

Iterators 和指针很类似,它定义了如下基本操作:
1)获取一个 Iterators 的值:int x = *iter;
2)自增、自减 Iterators:iter++, iter--;
3)可以用 != < 等比较运算符对不同的 Iterators 进行比较;
4)Iterators 移动动指定的偏移位置,如 iter += 20(iter 前移 20 个元素的位置);
5)获取两个 Iterators 间的距离:int distance = iter2 - iter1;
但是和指针不同的是,Iterators 提供了更多的功能,它不仅可以操作 container,还可以进行范围检查、容器分析等。
Iterators 最大的优点是实现了代码重用:基于 Iterators 实现的算法,可以用在不同的 containers 中。
但是并不是所有的 Iterators 都提供了相同的功能,我们可以把 Iterators 大致分为 “普通的 Iterators” 和 “随机访问的 Iterators”。对于普通 Iterators,我们可以用 ==、!= 等比较运算符进行比较,也可以自增、自减,但是它们不可以相减,也不可以给普通 Iterators 增加指定的值。
基于 Iterators,前面的程序可以这样实现:

 template<typename T> void reverse_array(T *first, T *last) { 
      if(first != last) { 
           while(true) { 
                swap(*first, *last); 
                first++; 
                if(first == last) { 
                     break; 
                } 
                last--; 
                if(first == last) { 
                     break; 
                } 
           } 
      } 
 }

这个程序和前面的程序的最大区别在于:不用 < 进行比较,而是用 == 。
通常 STL 的算法都会用两个 Iterators,一个 begin iterator、一个 end iterator,begin 指向 container 的第一个元素,end 指向 container 最后一个元素的下一个位置。每个 STL container 都有两个函数 begin()、end(),分别用于获取该 container 的 begin iterator 和 end iterator。
我们可以用 c.begin()==c.end() 来判断 container 是否为空,用 c.end()-c.begin() 来获取 container 的元素个数(和 c.size() 等效),当然并不是所有 container 都可以用这个相减操作。
STL 源码里的反转函数是这样的:

 template<typename T> void reverse_array_stl_compliant(T *begin, T *end) { 
      // We should at first decrement 'end' 
      // But only for non-empty range 
      if(begin != end) 
      { 
           end--; 
           if(begin != end) { 
                while(true) { 
                     swap(*begin, *end); 
                     begin++; 
                     If(begin == end) { 
                          break; 
                     } 
                     end--; 
                     if(begin == end) { 
                          break; 
                     } 
                } 
           } 
      } 
 } 

注意这和 C++ 标准库的 std::reverse(T begin, T end) 函数是不一样的。

此外,因为模板的强大作用,STL 的 algorithms 和 functions 可以接收多种类型的 iterator:

 vector<int> v; 
 // ... 
 vector<int> v2(v); 
 vector<int> v3(v.begin(), v.end()); // v3 equals to v2 

 int data[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 }; 
 vector<int> primes(data, data+(sizeof(data) / sizeof(data[0]))); 

最后一行代码通过 C 的原始数组创建一个 vector。

我们还可以这样来创建一个新的 vector:

 vector<int> v; 
 // ... 
 vector<int> v2(v.begin(), v.begin() + (v.size()/2)); 

如上 v2 等于 v1 的前半段。

下面是 reverse() 函数的应用实例(只反转指定区域的元素):

int data[10] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 }; 
 reverse(data+2, data+6); // the range { 5, 7, 9, 11 } is now { 11, 9, 7, 5 }; 

每个 container 都有两个函数 rbegin() 和 rend(),用于返回反向的 iterators,rbegin() 指向结尾、rend() 指向开头。在需要反向遍历 container 的时候,可用这两个函数。

 vector<int> v; 
 vector<int> v2(v.rbegin()+(v.size()/2), v.rend()); 

上面用 v 的前半段来创建 v2,v2 的内容是 v 的前半段,但是元素顺序刚好相反。

在创建 iterator 对象的时候,必须指定它的类型,类型指定可以通过在 container 后加上 ::iterator、::const_iterator、::reverse_iterator、::const_reverse_iterator 等来实现,如我们可以这样来遍历一个 vector:

 vector<int> v; 

 // ... 

 // Traverse all container, from begin() to end() 
 for(vector<int>::iterator it = v.begin(); it != v.end(); it++) { 
      *it++; // Increment the value iterator is pointing to 
 } 

出于性能方面的考虑,建议用 != 代替 <,用 empty() 代替 size()!=0。

find() 函数可用于查找指定范围内是否有某个元素,如果存在,那么返回指向第一个指定元素的 iterator,如果不存在,那么返回值等于 end interator:

 vector<int> v; 
 for(int i = 1; i < 100; i++) { 
      v.push_back(i*i); 
 } 

 if(find(v.begin(), v.end(), 49) != v.end()) { 
      // ... 
 }

如果想获取所找到的元素的下标,那么就用相减操作:

 int i = (find(v.begin(), v.end(), 49) - v.begin(); 
 if(i < v.size()) { 
      // ... 
 } 

注意用到 STL 的 algorithms 的时候,要 #include 。

下面还有些常用的方法:

int data[5] = { 1, 5, 2, 4, 3 }; 
 vector<int> X(data, data+5); 
 int v1 = *max_element(X.begin(), X.end()); // 返回 vector 中的最大值 
 int i1 = min_element(X.begin(), X.end()) – X.begin; // 返回 vector 中最小元素的下标 

 int v2 = *max_element(data, data+5); // 返回 array 中最大的元素 
 int i3 = min_element(data, data+5) – data; // 返回 array 中最小元素的下标 

我们可以定义一个宏:

#define all(c) c.begin(), c.end() 

然后有下面的排序方法:

vector<int> X; 
 // ... 
 sort(X.begin(), X.end()); // 升序排序 
 sort(all(X)); // 升序排序, 用 #define 
 sort(X.rbegin(), X.rend()); // 用 iterator 进行降序排序 

5. Compiling STL Programs

首先我们来看一下一个经典的错误:

 void f(const vector<int>& v) { 
      for( 
           vector<int>::iterator it = v.begin(); // 这里有错,看出来了吗
           // ... 
      // ... 
 }

错误在于形参被 const 修饰了,但是我们获取的 iterator 不是 const 类型,应该改成

 void f(const vector<int>& v) { 
      int r = 0; 
      // Traverse the vector using const_iterator 
      for(vector<int>::const_iterator it = v.begin(); it != v.end(); it++) { 
           r += (*it)*(*it); 
      } 
      return r; 
 } 

然后我们来简单介绍一下一个 GNU C 标准里特有的扩展: typeof,typeof 用于自动推导后边 () 里的数据类型,如:
typeof(a+b) x=(a+b)
这行代码定义了一个变量 x,x 的类型和 a+b 的结果类型一样,这就是这里 typeof(a+b) 的作用。typeof 还可用于 container 的遍历:

#define tr(container, it) \ 
      for(typeof(container.begin()) it = container.begin(); it != container.end(); it++) 

因为 typeof 的自动类型推导功能,我们可以利用这个宏遍历任意类型的 container,如

 void f(const vector<int>& v) { 
      int r = 0; 
      tr(v, it) { 
           r += (*it)*(*it); 
      } 
      return r; 
 } 

对 vector 来说,可能这个宏定义的作用并不大,但是当我们要遍历复杂的数据类型的时候,就非常有用了,可以省掉很多代码。

6. stl教程之 vector 中的数据操作

我们可以用 insert() 函数向指定的位置插入一个新的元素:

 vector<int> v; 
 // ... 
 v.insert(1, 42); // Insert value 42 after the first 

这样,从 第二个元素(index=1)开始的所有元素都要向后移一个位置,以给新的元素腾出空间。如果我们要批量插入元素,这样调用这个函数效率就比较低了,因为插入一个元素,所有后面的元素都要身后移动。这里我们可以这样调用:

 vector<int> v; 
 vector<int> v2; 
 // .. 
 // Shift all elements from second to last to the appropriate number of elements. 
 // Then copy the contents of v2 into v. 
 v.insert(1, all(v2)); 

把从第二个元素开始的所有元素都向后移动适当的位置,然后把 v2 中的所有内容填充到 v 移动元素后腾出的空间中。
同样的,erase() 函数也有两个类似的版本:

 erase(iterator); 
 erase(begin iterator, end iterator); 

第一个函数删除指定位置的元素,第二个删除指定区域的所有元素。

7. stl教程之String(字符串)

string 是 STL 中用于操作字符串的 container,它和 vector 是不一样的。主要的区别在于它们的字符串操作和内存管理方式策略。
String 有一个 substr() 函数,这个函数只需要通过下标值操作,不依赖于 iterators,如:

 string s = "hello"; 
 string 
      s1 = s.substr(0, 3), // "hel" 
      s2 = s.substr(1, 3), // "ell" 
      s3 = s.substr(0, s.length()-1), "hell" 
      s4 = s.substr(1); // "ello" 

8. stl教程之Set(集合)

STL 的 Set(集合) 有以下基本性质或操作:
1)任意两元素都是不相等的;
2)可添加、删除元素;
3)可获取元素的个数;
4)可查询集合中是否有指定元素(可在 O(logN) 时间内完成)。

先来看个简单的例子:

 set<int> s; 

 for(int i = 1; i <= 100; i++) { 
      s.insert(i); // Insert 100 elements, [1..100] 
 } 

 s.insert(42); // does nothing, 42 already exists in set 

 for(int i = 2; i <= 100; i += 2) { 
      s.erase(i); // Erase even values 
 } 

 int n = int(s.size()); // n will be 50 

Set 是没有 push_back() 函数的,因为它的元素不需要顺序,也正是这个原因,也不可通过下标来访问集合中的元素。遍历 Set 的唯一方法是用 iterators:

 // Calculate the sum of elements in set 
 set<int> S; 
 // ... 
 int r = 0; 
 for(set<int>::const_iterator it = S.begin(); it != S.end(); it++) { 
      r += *it; 
 } 

利用宏进行遍历会更好些,想象一下如果有定义 set< pair<string, pair< int, vector > >,我们怎么定义这玩意呢?所以还是用宏吧:

 set< pair<string, pair< int, vector<int> > > SS; 
 int total = 0; 
 tr(SS, it) { 
      total += it->second.first; 
 } 

注意 it->second.first,因为 it 是个 iterator,所以我们得取到它的值,才能进行操作。这句代码也可以这样写 (*it).second.first 。
要查找集合中是否指定元素,可用 find() 函数,但是注意了,STL 的全局 algorithm 里面也有个 find() 函数,也可以用于 Set 的查找操作,但是它的复杂度是 O(N)。而 Set 本身的 find() 方法,是为 Set 量身定做的,其算法复杂度为 O(logN),类似的 multiset、multimap、hash_map、hash_set 中也有类似方法。

 set<int> s; 
 // ... 
 if(s.find(42) != s.end()) { 
      // 42 presents in set 
 } 
 else { 
      // 42 not presents in set 
 } 

另一个复杂度为 O(logN) 的函数是 count() 函数:

 if(s.count(42) != 0) { 
      // … 
 } 

或者

 if(s.count(42)) { 
      // … 
 } 

但是,我个人倾向于用宏来实现和 count() 函数一样的功能,用于 Set 中元素的 count,因为要 count 的元素要么存在,要么不存在于 Set 中,没有数量这个说法:

 #define present(container, element) (container.find(element) != container.end()) 
 #define cpresent(container, element) (find(all(container),element) != container.end()) 

all(c) 展开为 c.begin(), c.end(前面我们定义过了)。
present 用于一般的 container,cpresent 用于 vector。

从集合中删除元素,可以用 erase() 函数:

 set<int> s; 
 // … 
 s.insert(54); 
 s.erase(29); 

erase() 也可以指定区间:

set<int> s; 
 // .. 
 set<int>::iterator it1, it2; 
 it1 = s.find(10); 
 it2 = s.find(100); 
 // Will work if it1 and it2 are valid iterators, i.e. values 10 and 100 present in set. 
 s.erase(it1, it2); // Note that 10 will be deleted, but 100 will remain in the container 

同样的,其构造函数也可以用指定的区间作为参数:

 int data[5] = { 5, 1, 4, 2, 3 }; 
 set<int> S(data, data+5); 

还可以利用 Set 把 vector 中重复的元素去掉并排序:

 vector<int> v; 
 // … 
 set<int> s(all(v)); 
 vector<int> v2(all(s)); 

这样,v2 会包含 v 中所有的元素,且按升序排序,且没有重复的元素。Set 中所有可比较的元素都可以排序。

9. stl教程之Map(映射)

Map 的简单示例如下:

 map<string, int> M; 
 M["Top"] = 1; 
 M["Coder"] = 2; 
 M["SRM"] = 10; 

 int x = M["Top"] + M["Coder"]; 

 if(M.find("SRM") != M.end()) { 
      M.erase(M.find("SRM")); // or even M.erase("SRM") 
 } 

其实 Map 和 Set 很相像,只是 Map 包含的不是 values,而是 <key, value>,即键值对。Map 保证了 key 的唯一性,而且还支持 [] 运算符。我们可以用前面定义过的 tr() 宏轻松实现对 map 的遍历。Map 的 iterator 是标准的 std::pair 类型,所以取值语句为 it->second:

 map<string, int> M; 
 // … 
 int r = 0; 
 tr(M, it) { 
      r += it->second; 
 } 

注意在遍历的时候,不要修改任何元素的 key,因为这样会破坏其实体完整性。

Map 的 map::find() 函数和 map::operator[] 有个很大的区别是:map::find() 不会改变 map 的内容,而 operator[] 会在元素不存在的时候,创建一个对应的元素。如果你不想往 map 中添加元素,那就不要用 operator[] 。如果形参是被 const 修饰的 map,那么 operator[] 也是不可用的:

 void f(const map<string, int>& M) { 
      if(M["the meaning"] == 42) { // Error! Cannot use [] on const map objects! 
      } 
      if(M.find("the meaning") != M.end() && M.find("the meaning")->second == 42) { // Correct 
           cout << "Don't Panic!" << endl; 
      } 
 } 

10. Notice on Map and Set

在底层实现上,map 和 set 都是基于红黑树的,底层的实现细节,我们不用太过关心。我们要记住的是:map 和 set 本身就以升序的顺序存储元素,在遍历时,map 和 set 总以升序的顺序遍历,所以说最好不要在遍历的过程中修改 map 或 set 的 key。在算法实现上,map 和 set 是有序的这个特点非常有用。
还有重要的一点:map 和 set 的 iterators 也可用 ++ 和 -- 运算符。如下,假设 set 中有一个元素 42,且它的前驱和后续都存在,那么下面的代码就有效:

 set<int> S; 
 // ... 
 set<int>::iterator it = S.find(42); 
 set<int>::iterator it1 = it, it2 = it; 
 it1--; 
 it2++; 
 int a = *it1, b = *it2; 

这样我们就得到 42 的左右邻居结点了。

11. More on algorithms

STL 的 algorithms 都声明在 #include 头文件中,其中最基本的四个函数:
1)min(a, b),求两者中的小者;
2)max(a, b),求两者中的大者;
3)swap(a, b),交互两个元素的值。
4)sort(begin, end),用于对指定区域进行升序排序,但是 sort() 函数只能对 random access iterators 进行排序,所以并不是所有的 containers 都支持 sort() 函数。注意 set 和 map 都是有序的,不需要再用 sort() 进行排序了。

还有两个重要的函数 next_permutation(begin, end) 和 prev_permutation(begin, end),分别用于返回指定内的元素的下一个排列和上一个排列,对 next_permutation() 函数,如果已经是最后一个排列, 则返回 false;对 prev_permutation() 函数,如果已是最前面一个排列,则返回 false。但是注意,在调用 next_permutation() 函数前,要先保证 container 已经排好序了:

 vector<int> v; 

 for(int i = 0; i < 10; i++) { 
      v.push_back(i); 
 } 

 do { 
      Solve(..., v); 
 } while(next_permutation(all(v)); 

12. stl教程之String Streams

C++ 有两个 istringstream 和 ostringstream 用于处理 input、output 流,它们都定义在 #include
1)istringstream 用于 standard input 读取数据:

 void f(const string& s) { 

      // Construct an object to parse strings 
      istringstream is(s); 

      // Vector to store data 
      vector<int> v; 

      // Read integer while possible and add it to the vector 
      int tmp; 
      while(is >> tmp) { 
           v.push_back(tmp); 
      } 
 } 

2)ostringstream 用于格式化 output:

 string f(const vector<int>& v) { 

      // Constucvt an object to do formatted output 
      ostringstream os; 

      // Copy all elements from vector<int> to string stream as text 
      tr(v, it) { 
           os << ' ' << *it; 
      } 

      // Get string from string stream 
      string s = os.str(); 

      // Remove first space character 
      if(!s.empty()) { // Beware of empty string here 
           s = s.substr(1); 
      } 

      return s; 
 } 

13. stl教程总结

我总结了一些常用宏,可用在 TopCoder 中:

 typedef vector<int> vi; 
 typedef vector<vi> vvi; 
 typedef pair<int,int> ii; 
 #define sz(a) int((a).size()) 
 #define pb push_back 
 #defile all(c) (c).begin(),(c).end() 
 #define tr(c,i) for(typeof((c).begin() i = (c).begin(); i != (c).end(); i++) 
 #define present(c,x) ((c).find(x) != (c).end()) 
 #define cpresent(c,x) (find(all(c),x) != (c).end()) 
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Storm-Shadow

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值