STL 容器用法简要整理(未完成)
STL 容器用法简要整理
本文将简要介绍 C++14 中可以使用的 STL 容器的用法。根据 CCF 规定,这些容器都可以在比赛中使用。
本文中的代码均为 C++14。
本文中的代码均已引入了相关的库,并 using namespace std。
共同点
-
特性:所有能用下标访问的 STL 容器,下标都是从
0开始,到size - 1结束,如vector,array,string。 -
初始化:STL 容器的初始化都是形如
container<T> name的形式,其中T是数据类型,如int,char,结构体或其它STL容器;name是你起的容器的名字。例如,创建一个空的int型vector可以这么写:vector<int> v。 -
=:赋值运算符。可以把某个容器的值赋值给另一个同类容器。以vector为例:int main() { vector<int> v1{1, 2, 3, 4, 5}; vector<int> v2 = v1; // 两个容器的类型必须相同。vector<int> 和 vector<long long> 也是不能互相赋值的。 for(int x: v2) cout << x << ' '; cout << endl; return 0; }输出:
1 2 3 4 5。 -
size():返回容器内的元素个数。(如无特别说明,提到的函数都是成员函数。) -
empty():bool型,返回容器是否为空。 -
swap():a.swap(b)表示交换容器a和容器b的值。时间复杂度常数。但是有一个例外:array的swap()是线性的!(参见此处。作为对比,vector和 map 的时间复杂度都是常数(constant)。)int main() { vector<int> v1{1, 2, 3, 4, 5}; vector<int> v2{6, 7, 8, 9, 10}; v1.swap(v2); cout << "v1: "; for(int x: v1) cout << x << ' '; cout << endl; cout << "v2: "; for(int x: v2) cout << x << ' '; cout << endl; return 0; }输出:
v1: 6 7 8 9 10 v2: 1 2 3 4 5 -
>/>=/</<=/==/!=:按字典序比较两个容器。无序容器(如unordered_map)只支持==和!=。int main() { vector<int> v1{1, 2, 3, 4, 5}, v2{1, 2, 4, 2, 1}, v3{1, 2, 3, 4, 5, 6}; cout << "v1 < v2: " << boolalpha << (v1 < v2) << endl; cout << "v1 < v3: " << boolalpha << (v1 < v3) << endl; string s1("cat"), s2("cat"), s3("cap"); cout << "s1 == s2: " << boolalpha << (s1 == s2) << endl; cout << "s1 < s3: " << boolalpha << (s1 < s3) << endl; return 0; }输出:
v1 < v2: true v1 < v3: true s1 == s2: true s1 < s3: false -
迭代器(iterator):迭代器类似指针。用
*可以解引用。声明时可以用
auto:auto it = v1.begin(),也可以用container::iterator:vector<int>::iterator it。begin()返回指向容器开头的迭代器,end()返回指向容器最后一个元素的后继的迭代器。(所以实际上end()不指向任何一个元素,元素的地址范围是[begin(), end())。)对于
vector,array等可以用下标访问的容器,可以用*(it + c)的形式来访问元素,其中it是迭代器,c是整数。也可以用这种形式修改元素,就像指针一样。这里以array为例:int main() { array<int, 5> a{1, 2, 3, 4, 5}; auto it = a.begin(), it2 = a.end(); cout << *it << ' ' << *(it + 1) << ' ' << *(it2 - 1) << endl; *it = 2, *(it + 1) = 10; for(int x: a) cout << x << ' '; cout << endl; return 0; }输出:
1 2 5 2 10 3 4 5更多关于迭代器的知识,参见此处。
vector(向量)
std::vector - cppreference.com
可变长度数组。解决了 C 风格数组长度只能是常数的问题。它支持如下操作:\(O(1)\) 的随机访问,\(O(n)\) 的插入/删除元素。
我们经常能看到这样的题目(尤其是在 CF 上):有一个 \(n \times m\) 的矩阵,我们要存储每个元素的信息,而 \(nm \le 10^6\),但对于 \(n\),\(m\) 的大小没有单独限定。这时候用数组就不能保证既不爆空间,又能存下所有数据。vector 的作用就体现出来了。
注意,不要使用 vector<bool>。vector<bool> 不是 vector,使用它可能会出现意外的错误。如必须使用,可以用 vector<char> 代替,二者空间相同。
需要注意的是,vector 的常数有时劣于数组(待验证)。
-
初始化:
vector的初始化方式有许多种,使用合理的方式可以为我们提供便利。参见此处。下面是 5 种常用的初始化方法:
#define vec_print(x) cout << #x": ", print(x) void print(vector<int> &v) { for(int x: v) cout << x << ' '; cout << endl; } int main() { vector<int> v1; // 1. 创建空 vector,时间复杂度常数 int n = 5; vector<int> v2(n); // 2. 创建长度为n的vector,时间复杂度线性。 // 元素的初值都为0。下面会讨论这个初值是怎么得来的。 // 这体现了与数组的不同之处:长度可以是变量。 vector<int> v3(n, 42); // 3. 创建长度为n的vector,每个元素都是42。时间复杂度线性。 vector<int> v4(v3); // 4. 创建一个与v3相同的vector。时间复杂度与v3的大小线性相关。 vector<int> v5(v4.begin() + 1, v4.end() - 1); // 5. 把[v4[1] ~ v4[4])中的元素拷贝到v5中(注意是左闭右开区间!) // 时间复杂度和拷贝的元素数量线性相关。 vector<int> v6{1, 2, 3, 4, 5}; // 6. 用列表初始化,时间复杂度? // (我不知道这个方法的时间复杂度,但是元素太多的时候一般不会用列表初始化,因此时间可以忽略不计) vec_print(v1), vec_print(v2), vec_print(v3), vec_print(v4), vec_print(v5), vec_print(v6); return 0; }输出:
v1: v2: 0 0 0 0 0 v3: 42 42 42 42 42 v4: 42 42 42 42 42 v5: 42 42 42 v6: 1 2 3 4 5关于方法 2 中,元素的初值:
好吧,我没有完全搞懂。cppreference 上的原文表示元素的值是
default-inserted instance of T,但我不知道什么叫 "default-inserted"。我猜测应该是元素默认的值:例如整型默认是0。如果元素类型是结构体,而结构体有构造函数,那么元素的初值通过构造函数得来:
struct Node { int x; Node(): x(42) {} }; int main() { vector<Node> v(5); for(auto nd: v) cout << nd.x << ' '; cout << endl; return 0; }输出:
42 42 42 42 42关于方法 5:
用某个
vector中一段元素的值初始化另一个vector,两个vector的元素类型可以不相同,但要满足可以互相转换。例如int可以转成long long。或者,如果被初始化的vector的元素是结构体,要有对应的构造函数。需要注意的是,如果两个
vector的元素类型不同,就不能用vector<int> v2(v1)之类的形式来初始化,即使两种元素类型可以互相转换或者有构造函数也不行。struct Node { string str; Node(int x): str(to_string(x * 10) + "str") {} }; int main() { vector<int> v1{1, 2, 3, 4, 5}; vector<long long> v2(v1.begin(), v1.end()); // ok,int 和 long long 可以转换 // vector<string> v3(v1.begin(), v1.end()); // wrong,int 不能转成 long long vector<Node> v4(v1.begin(), v1.end()); // ok,存在 int 到 Node 的构造函数 // vector<Node> v5(v1); // wrong,不同元素类型的 vector 不能互相初始化 cout << "v1: "; for(auto x: v1) cout << x << ' '; cout << endl; cout << "v2: "; for(auto x: v2) cout << x << ' '; cout << endl; cout << "v4: "; for(auto x: v4) cout << x.str << ' '; cout << endl; return 0; }输出:
v1: 1 2 3 4 5 v2: 1 2 3 4 5 v4: 10str 20str 30str 40str 50str
以下是一些常用的成员函数(STL 共有的成员函数不再列出):
-
访问元素的方式:
- 通过
[]访问。 - 通过
at()访问。与前者的区别是用at访问时会检测有没有越界。at()的效率低于[],所以一般情况下都用[],但是如果觉得自己可能会RE,可以用at()以便于调试。 front()访问首元素,back()访问尾元素。(注意与begin()和end()区分,它们是迭代器。)- 用迭代器访问:
*it表示it指向的元素的值,例如*begin()就表示首元素的值。
int main() { vector<int> v{1, 2, 3, 4, 5}; cout << v.front() << ' ' << v[1] << ' ' << v.at(2) << ' ' << *(v.begin() + 3) << ' ' << v.back() << endl; return 0; }输出:
1 2 3 4 5用
at()访问时,如果越界,会输出错误信息。int main() { vector<int> v{1, 2, 3, 4, 5}; cout << v.at(5) << endl; return 0; }输出:
terminate called after throwing an instance of 'std::out_of_range' what(): vector::_M_range_check: __n (which is 5) >= this->size() (which is 5) - 通过
-
添加元素:
push_back():向vector的末尾增加元素。这么做会增加vector的长度。注意我们没有push_front(),要实现类似操作,得用deque。 -
删除元素:
pop_back()。同理,没有pop_front()。 -
改变
vector的大小:resize()。resize(n)表示把大小改为n。如果原先的长度大于n,会删除多余的元素;否则:- 如果调用
resize(n),会在末尾添加元素的默认值; - 如果调用
resize(n, val),会在末尾补上val。
时间复杂度和
n与vector原来大小的差线性相关。int main() { vector<int> v{1, 2, 3, 4, 5}; v.resize(3); for(int x: v) cout << x << ' '; cout << endl; v.resize(10, 42); for(int x: v) cout << x << ' '; cout << endl; cout << "v.size() is " << v.size() << endl; return 0; }输出:
1 2 3 1 2 3 42 42 42 42 42 42 42 v.size() is 10 - 如果调用
-
assign():给vector填充某个元素,有点像fill()。用法:assign(n, val):把vector替换为n个val。时间复杂度 \(O(n)\)。vector的大小多退少补。“替换”的意思是会覆盖原有的元素。- 咕咕咕
-
insert,erase():插入删除操作。通常情况下不使用这个函数,因为时间复杂度是线性。如果要做到常数的插入和删除,应该使用链表。这里不做介绍。
array(数组)
定长数组。和 C 中的数组一样,长度必须为常数,效率优于 vector,和 C 中的数组相当。个人建议用 array 代替所有的定长数组,用 vector 代替所有的不定长数组。
大部分使用方法与 vector 相同,除了不能加入/删除末尾元素(push_back()/pop_back()),当然也不能 insert() 和 erase()。
下面是一些(个?)特有的函数:
-
fill():fill(x)表示把array全部填充为x。时间复杂度与array大小线性相关。int main() { array<char, 5> a{'a', 'a', 'a', 'a', 'a'}; a.fill('b'); for(char ch: a) cout << ch; cout << endl; return 0; }输出:
bbbbb(覆盖了原先的值)
deque(双端队列/双端数组)
咕咕咕
string(字符串)
(严格来说 string 不算 container,但它很重要,这里一并整理用法。)
在 C 中,字符串是通过 char 数组实现的。而在 C++ 中,我们终于有了原生的字符串类型——string。它自带许多高效的函数,可以为我们写题(特别是字符串相关的模拟题)带来极大便利。与此同时,它的常数也十分优秀。
-
初始化:
int main() { string s1; // 1. 创建空字符串 string s2("test"); // 2. 创建特定字符串 int n = 5; string s3(n, '='); // 3. 创建含n个'='的字符串,时间复杂度线性 auto print = [&](string name, string str) {cout << name << ": " << str << endl;}; print("s1", s1), print("s2", s2), print("s3", s3); return 0; }输出:
s1: s2: test s3: ===== -
find():查找某个子串/字符。如果存在该子串,返回第一个子串的第一个字符的下标;否则,返回string::npos(本质上是一个size_t型的数)。int main() { string str("This is a string."); /* ^ ^ ^ 2 5 15 */ cout << str.find("This") << ' ' << str.find("is") << endl; cout << str.find('g') << endl; // 也可以找 char cout << str.find("is", 3) << endl; // find(s, pos):从下标pos处寻找s cout << str.find("1234") << ' ' << boolalpha << (str.find("1234") == string::npos) << endl; // 找不到示例 return 0; }输出:
0 2 15 5 18446744073709551615 true其中
18446744073709551615就是string::npos,具体的值由编译器决定。 -
substr()

浙公网安备 33010602011771号