STL-----容器
1. 序列容器
![](https://i-blog.csdnimg.cn/blog_migrate/2c941813bc1bea60e2f5188afb7394ee.png)
1.1 array
- array< T,N >:数组容器,是一个长度固定的序列,有 N 个 T 类型的对象,不能增加或删除元素。
初始化:
访问元素std::array<int,100> data; std::array<int, 100> data {}; std::array<double, 10> values {0.5, 1.0, 1.5, 2.0}; //通过调用数组对象的成员函数 fill(),可以将所有元素设成给定值。 values.fill(3.1415926);
迭代器用法values[4] = values[3] + 2.O*values[1]; //当传给 at() 的索引是一个越界值时,这时会抛出 std::out_of_rang 异常。 values.at(4) = values.at(3) + 2.O*values.at(1);
unsigned int h {min_ht}; auto first = height_ins.begin(); auto last = height_ins.end() ; while (first != last) { *first++ = h; h += ht_step; }
1.2 vector
-
vector< T >:向量容器。是一个长度可变的序列,用来存放T类型的对象。必要时,可以自动增加容量,但只能在序列的末尾高效地增加或删除元素。
初始化://初始化为20个0 std::vector<double> values(20,0); //初始化为1个20 std::vector<double> values{20}; //words_copy 被 words 数组容器中的元素初始化 std::array<std :: string, 5> words {"one", "two","three", "four", "five"}; std::vector<std::string> words_copy {std::begin(words) , std::end(words)};
容量和大小
访问元素std::vector<double> v; v.reserve(20); std::vector<double> values (20); values[0] = 3.14159; values[1] = 5.0; values.at(2) = 2.0*values[0]*values[1]; //vector 的成员函数 front() 和 back() 分別返回序列中第一个和最后一个元素的引用 std::cout << values.front () << std::endl; // Outputs 3.14159 //因为成员函数 front() 和 back() 返回的是引用,所以它们可以出现在赋值运算符的左边。 values.front() = 2.71828; //成员函数 data() 返回一个指向数组的指针,它在内部被用来存储元素。 auto pData = values.data();
迭代器用法
vector 容器的迭代器是随机访问迭代器。当然,也可以通过全局函数获取它们。vector 有成员函数 push_back(),所以能够通过使用 back_insert_iterator 来添加新的值。从前面章节了解到,可以通过调用全同的 back_inserter() 函数来获取一个后向插入迭代器。无法在 vector 奔器中使用 front_insert_iterator,这需要 vector 有成员函数 push_front(),但是 vector 容器并没有定义这个函数。
可以通过演示如何用 copy() 算法来添加元素,向你展示怎样在 vector 中使用后向插入迭代器。copy() 的头两个参数是两个迭代器,指定了复制元素的范围,第三个参数指定了这些元素存放的位置。头两个参数要求是输入迭代器,所以可以接受任何其他类别的迭代器;显然第三个参数必须是一个输出迭代器。这里有一个示例:
std::vector<double> data {32.5, 30.1, 36.3, 40.0, 39.2}; std::cout << "Enter additional data values separated by spaces or Ctrl+Z to end:" << std::endl; std::copy(std::istream_iterator<double>(std::cin),std::istream_iterator<double>(), std::back_inserter(data)); std::copy(std::begin(data), std::end(data),std::ostream_iterator<double> (std:: cout," "))
添加元素
//push_back() std::vector<double> values; values.push_back(3.1415926); //empalce_back() std::vector<std::string> words; words.push_back (std:: string ("facetious") ) ; // Calls string constructor & moves the string object words.emplace_back("abstemious");// Calls string constructor to create element in place std::string str {"alleged"}; words.emplace_back(str, 2, 3); // Create string object corresponding to "leg" in place
插入元素
通过使用成员函数 emplace(),可以在 vector 序列中插入新的元素。emplace() 的第一个参数是一个迭代器,它确定了对象生成的位置。对象会被插入到迭代器所指定元素的后面。第一个参数后的参数,都作为插入元素的构造函数的参数传入。
成员函数 insert() 可以在 vector 中插入一个或多个元素。第一个参数总是一个指向插入点的 const 或 non-const 迭代器。元素会被迅速插入到第一个参数所指向元素的前面,如果第一个参数是一个反向迭代器,元素会被插入到迭代器所指向元素的后面。
删除元素
vector成员函数clear()删除所有元素,pop_back()来删除容器尾部元素。erase()传入两个元素,用来删除指定元素的范围。remove()在头两个参数指定的元素范围内,移除了所有匹配 remove() 的第三个参数 string(“none”) 的元素。
1.3 deque
- deque < T >:双端队列容器,完成了标准 C++ 数据结构中栈的所有功能。是一个长度可变的、可以自动增长的序列,在序列的两端都不能高效地增加或删除元素。
//初始化 std::deque<int> my_deque(10,1); //容量大小size() //访问元素 front() and back() //添加删除元素 my_deque.push_front(11); my_deque.push_back(2); my_deque.pop_front(); //emplace_back(),emplace_front(),emplace(),insert(),erase(),clear()这些成员函数与vector容器的使用方法相同。 //修改(替换)元素assign()
1.4 list
-
list < T >:双向链表容器,完成了标准 C++ 数据结构中链表的所有功能。是一个长度可变的、由 T 类型对象组成的序列,它以双向链表的形式组织元素,在这个序列的任何地方都可以高效地增加或删除元素。
//初始化 std::list<std::string> words; std::list<double> values(50, 3.14159265); //获取begin(),end(),front(),back() //增加插入元素 std::list<std::string> names { "Jane", "Jim", "Jules", "Janet"}; names.push_front("Ian"); // Add string ("Ian") to the front of the list names.push_back("Kitty"); // Append string ("Kitty") to the end of the list names.emplace_front("Ian");//Create string ("Ian") in place at the front of the list names.emplace_back("Kitty");// Create string ("Kitty") in place at the end of the list std::list<int> data(10, 55); // List of 10 elements with value 55 data.insert(++begin(data), 66); // Insert 66 as the second element auto iter = begin(data); std::advance(iter, 9); // Increase iter by 9 data.insert(iter, 3, 88);// Insert 3 copies of 88 starting at the 10th //删除元素 std::list<int> numbers { 2, 5, 2, 3, 6, 7, 8, 2,}; numbers.remove(2); // List is now 5 3 6 7 8 9 numbers.remove_if([](int n){return n%2 == 0;});// Remove even numbers. Result 5 3 7 9 //排序及元素合并 std::list<std::string> my_words { "three","six", "eight"}; std::list<std::string> your_words { "seven", "four", "nine"}; auto comp_str = [](const std::strings s1, const std::strings s2){ return s1[0]<s2[0];}; my_words.sort (comp_str); //"eight" "six" "three" your_words.sort (comp_str) ; //"four" "nine" "seven" my_words.merge (your_words, comp_str) ; // "eight" "four" "nine" "six" "seven" "three" //splice() 的第一个参数是指向目的容器的迭代器。第二个参数是元素的来源。第三个参数是一个指向源list容器中被粘接元素的迭代器,它会被插入到第一个参数所指向位置之前。 std::list<std::string> my_words {"three", "six", "eight"}; std::list<std::string> your_words {"seven", "four", "nine"}; my_words.splice(++std::begin(my_words), your_words, ++std::begin(your_words)); //out: your_words: "seven", "nine" //out: my_words : "three", "four", "six", "eight"
-
forward list< T >:正向链表容器。是一个长度可变的、由 T 类型对象组成的序列,它以单链表的形式组织元素,是一类比链表容器快、更节省内存的容器,但是它内部的元素只能从第一个元素开始访问。
fdrward_list 和 list 最主要的区别是:它不能反向遍历元素;只能从头到尾遍历。
forward_list 的单向链接性也意味着它会有一些其他的特性:
无法使用反向迭代器。只能从它得到const或non-const前向迭代器,这些迭代器都不能解引用,只能自增;
没有可以返回最后一个元素引用的成员函数back();只有成员函数front();
因为只能通过自增前面元素的迭代器来到达序列的终点,所以push_back()、pop_back()、emplace_back()也无法使用。forward_list 的操作比 list 容器还要快,而且占用的内存更少,尽管它在使用上有很多限制,但仅这一点也足以让我们满意了。
forward_list 容器的构造函数的使用方式和 list 容器相同。forward_list 的迭代器都是前向迭代器。它没有成员函数 size(),因此不能用一个前向迭代器减去另一个前向迭代器,但是可以通过使用定义在头文件 iterator 中的 distance() 函数来得到元素的个数。例如:
std::forward_list<std::string> my_words {"three", "six", "eight"}; auto count = std::distance(std::begin(my_words),std::end(my_words)); // Result is 3
distance() 的第一个参数是一个开始迭代器,第二个参数是一个结束迭代器,它们指定了元素范围。当需要将前向迭代器移动多个位置时,advance() 就派上了用场。例如:
std::forward_list<int> data {10, 21, 43, 87, 175, 351}; auto iter = std::begin(data); size_t n {3}; std::advance(iter, n); std::cout << "The " << n+1 << "th element is n << *iter << std::endl; // Outputs 87
forward_list 和 list —样都有成员函数 sort() 和 merge(),它们也都有 remove()、remove_if() 和unique(),所有这些函数的用法都和 list 相同。
1.5 容器中常见的函数成员
表 1 array、vector 和 deque 容器的函数成员
表 2 list 和 forward_list 的函数成员
函数成员 | 函数功能 | list | forward_list |
---|---|---|---|
begin() | 返回指向容器中第一个元素的迭代器。 | 是 | 是 |
end() | 返回指向容器最后一个元素所在位置后一个位置的迭代器。 | 是 | 是 |
rbegin() | 返回指向最后一个元素的迭代器。 | 是 | - |
rend() | 返回指向第一个元素所在位置前一个位置的迭代器。 | 是 | - |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | 是 | 是 |
before_begin() | 返回指向第一个元素前一个位置的迭代器。 | - | 是 |
cbefore_begin() | 和 before_begin() 功能相同,只不过在其基础上,增加了 const 属性,即不能用该指针修改元素的值。 | - | 是 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | 是 | 是 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | 是 | - |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | 是 | - |
assign() | 用新元素替换原有内容。 | 是 | 是 |
operator=() | 复制同类型容器的元素,或者用初始化列表替换现有内容。 | 是 | 是 |
size() | 返回实际元素个数。 | 是 | - |
max_size() | 返回元素个数的最大值,这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。 | 是 | 是 |
resize() | 改变实际元素的个数。 | 是 | 是 |
empty() | 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。 | 是 | 是 |
front() | 返回容器中第一个元素的引用。 | 是 | 是 |
back() | 返回容器中最后一个元素的引用。 | 是 | - |
push_back() | 在序列的尾部添加一个元素。 | 是 | - |
push_front() | 在序列的起始位置添加一个元素。 | 是 | 是 |
emplace() | 在指定位置直接生成一个元素。 | 是 | - |
emplace_after() | 在指定位置的后面直接生成一个元素。 | - | 是 |
emplace_back() | 在序列尾部生成一个元素。 | 是 | - |
cmplacc_front() | 在序列的起始位生成一个元索。 | 是 | 是 |
insert() | 在指定的位置插入一个或多个元素。 | 是 | - |
insert_after() | 在指定位置的后面插入一个或多个元素。 | - | 是 |
pop_back() | 移除序列尾部的元素。 | 是 | - |
pop_front() | 移除序列头部的元素。 | 是 | 是 |
reverse() | 反转容器中某一段的元素。 | 是 | 是 |
erase() | 移除指定位置的一个元素或一段元素。 | 是 | - |
erase_after() | 移除指定位置后面的一个元素或一段元素。 | - | 是 |
remove() | 移除所有和参数匹配的元素。 | 是 | 是 |
remove_if() | 移除满足一元函数条件的所有元素。 | 是 | 是 |
unique() | 除所有连续重复的元素。 | 是 | 是 |
clear() | 移除所有的元素,容器大小变为 0。 | 是 | 是 |
swap() | 交换两个容器的所有元素。 | 是 | 是 |
sort() | 对元素进行排序。 | 是 | 是 |
merge() | 合并两个有序容器。 | 是 | 是 |
splice() | 移动指定位置前面的所有元素到另一个同类型的 list 中。 | 是 | - |
splice_after() | 移动指定位置后面的所有元素到另一个同类型的 list 中。 | - | 是 |
2. 容器适配器
2.1 stack
-
stack< T >:是一个封装了 deque 容器的适配器类模板,默认实现的是一个后入先出(Last-In-First-Out,LIFO)的压入栈。stack 模板定义在头文件 stack 中。
stack 容器适配器的模板有两个参数。第一个参数是存储对象的类型,第二个参数是底层容器的类型。
stack 的底层容器默认是 deque 容器,因此模板类型其实是 stack<typename T, typename Container=deque>。通过指定第二个模板类型参数,可以使用任意类型的底层容器,只要它们支持 back()、push_back()、pop_back()、empty()、size() 这些操作。堆栈操作
和其他序列容器相比,stack 是一类存储机制简单、所提供操作较少的容器。下面是 stack 容器可以提供的一套完整操作:- top():返回一个栈顶元素的引用,类型为 T&。如果栈为空,返回值未定义。
- push(const T& obj):可以将对象副本压入栈顶。这是通过调用底层容器的 push_back() 函数完成的。
- push(T&& obj):以移动对象的方式将对象压入栈顶。这是通过调用底层容器的有右值引用参数的 push_back() 函数完成的。
- pop():弹出栈顶元素。
- size():返回栈中元素的个数。
- empty():在栈中没有元素的情况下返回 true。
- emplace():用传入的参数调用构造函数,在栈顶生成对象。
- swap(stack & other_stack):将当前栈中的元素和参数中的元素交换。参数所包含元素的类型必须和当前栈的相同。对于 stack 对象有一个特例化的全局函数 swap() 可以使用。
2.2 queue
- queue< T >:是一个封装了 deque 容器的适配器类模板,默认实现的是一个先入先出(First-In-First-Out,LIFO)的队列。可以为它指定一个符合确定条件的基础容器。queue 模板定义在头文件 queue 中。
queue 和 stack 有一些成员函数相似,但在一些情况下,工作方式有些不同:- front():返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
- back():返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
- push(const T& obj):在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。
- push(T&& obj):以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。
- pop():删除 queue 中的第一个元素。
- size():返回 queue 中元素的个数。
- empty():如果 queue 中没有元素的话,返回 true。
- emplace():用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。
- swap(queue &other_q):将当前 queue 中的元素和参数 queue 中的元素交换。它们需要包含相同类型的元素。也可以调用全局函数模板 swap() 来完成同样的操作。
2.3 priority_queue
- priority_queue< T >:是一个封装了 vector 容器的适配器类模板,默认实现的是一个会对元素排序,从而保证最大元素总在队列最前面的队列。priority_queue 模板定义在头文件 queue 中。
priority_queue 模板有 3 个参数,其中两个有默认的参数;第一个参数是存储对象的类型,第二个参数是存储元素的底层容器,第三个参数是函数对象,它定义了一个用来决定元素顺序的断言。因此模板类型是:
对 priority_queue 进行操作有一些限制:template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>> class priority_queue
- push(const T& obj):将obj的副本放到容器的适当位置,这通常会包含一个排序操作。
- push(T&& obj):将obj放到容器的适当位置,这通常会包含一个排序操作。
- emplace(T constructor a rgs…):通过调用传入参数的构造函数,在序列的适当位置构造一个T对象。为了维持优先顺序,通常需要一个排序操作。
- top():返回优先级队列中第一个元素的引用。
- pop():移除第一个元素。
- size():返回队列中元素的个数。
- empty():如果队列为空的话,返回true。
- swap(priority_queue< T >& other):和参数的元素进行交换,所包含对象的类型必须相同。
3. 堆用法
堆(heaps) 是一种特殊的数据组织方式,STL 中的 priority_queue 容器适配器底层就是采用堆来组织数据存储的。
树 是分层排列的元素或节点。每个节点有一个键,它是节点中所保存的对象,就如同链表中的节点。父节点是有一个或两个子节点的节点。一般父节点可以有任意个数的子节点,树中的父节点不需要有相同个数的子节点。没有子节点的节点叫作叶节点。一般父节点的键与其子节点有一些关系。树都有一个根节点,它是树的基础,从根节点可以到达所有的子节点。
创建堆
用来创建堆的函数定义在头文件 中。max_heap() 对随机访问迭代器指定的一段元素重新排列,生成一个堆。默认使用的是 < 运算符,可以生成一个大顶堆。例如:
std::vector<double>numbers{2.5,10.0,3.5,6.5,8.0,12.0,1.5,6.0};
std::make_heap(std::begin(numbers), std::end(numbers));//{12 10 3.5 6.5 8 2.5 1.5 6}
调用 make_heap() 后,vector 中的元素如注释所示,这也说明了图 2 所展示的结构。
priority_queue 是一个堆。在底层,一个 priority_queue 实例创建了一个堆。在堆中,所有成对的连续元素不需要有相同的比较关系。图 2 所示堆中的前 3 个元素是顺序递减的,但第 4 个元素却大于第 3 个元素。既然如此,为什么 STL 有 priority_queue (它是一个堆),却还需要创建堆,特别是还需要将堆作为优先级队列?
这是因为 priority_queue 可以提供堆没有的优势,它可以自动保持元素的顺序;但我们不能打乱 priority_queue 的有序状态,因为除了第一个元素,我们无法直接访问它的其他元素。如果需要的是一个优先级队列,这一点非常有用。
从另一方面来说,使用 make_heap() 创建的堆可以提供一些 priority_queue 没有的优势:
- 可以访问堆中的任意元素,而不限于最大的元素,因为元素被存储在一个容器中,就像是我们自己的 vector。这也提供了偶然破坏元素顺序的可能,但是总可以调用 make_heap() 来还原堆。
- 可以在任何提供随机访问迭代器的序列容器中创建堆。这些序列容器包括普通数组、string 对象、自定义容器。这意味着无论什么时候需要,都可以用这些序列容器的元素创建堆,必要时,可以反复创建。甚至还可以为元素的子集创建堆。
如果使用保持堆顺序的函数,那么可以将堆当作优先级队列使用。
堆操作
堆不是容器,而是组织容器元素的一种特别方式。为了向堆中添加元素,首先可以用任何方法将元素附加到序列中。然后调用 push_heap() 来插入最后一个元素,为了保持堆的结构,这个元素会被重新排列到一个适当的位置。
std::vector<double> numbers {2.5, 10.0, 3.5, 6.5, 8.0, 12.0, 1.5, 6.0};
std::make_heap(std::begin(numbers),std::end(numbers));//{12 10 3.5 6.5 8 2.5 1.5 6}
numbers.push_back(11); // {12 10 3.5 6.5 8 2.5 1.5 6 11}
std::push_heap(std::begin(numbers), std::end(numbers));//{12 11 3.5 10 8 2.5 1.5 6 6.5}
push_back() 会在序列末尾添加元素,然后使用 push_heap() 恢复堆的排序。通过调用 push_heap(),释放了一个信号,指出我们向堆中添加了一个元素,这可能会导致堆排序的混乱。push_heap() 会因此认为最后一个元素是新元素,为了保持堆结构,会重新排列序列。
当然,也可以用自己的比较函数来创建堆,但是必须和 push_heap() 使用相同的比较函数:
std::vector<double> numbers {2.5, 10.0, 3.5, 6.5, 8.0, 12.0, 1.5, 6.0};
std::make_heap(std::begin(numbers), std::end(numbers), std::greater<>());//{1.5 6 2.5 6.5 8 12 3.5 10}
numbers.push_back(1.2);//{1.5 6 2.5 6.5 8 12 3.5 10 1.2}
std::push_heap(std::begin(numbers), std::end(numbers),std::greater<>());//{1.2 1.5 2.5 6 8 12 3.5 10 6.5}
如果 push_heap() 和 make_heap() 的第 3 个参数不同,代码就无法正常执行。
删除最大元素和添加元素到堆的过程有些相似,但所做的事是相反的。首先调用 pop_heap(),然后从容器中移除最大的元素,例如:
std::vector<double> numbers{2.5, 10.0, 3.5, 6.5, 8.0, 12.0, 1.5, 6.0};
std::make_heap(std::begin(numbers),std::end(numbers));//{12 10 3.5 6.5 8 2.5 1.5 6}
std::pop_heap(std::begin(numbers),std::end(numbers));//{10 8 3.5 6.5 6 2.5 1.5 12}
numbers.pop_back();//{10 8 3.5 6.5 6 2.5 1.5}
pop_heap() 函数将第一个元素移到最后,并保证剩下的元素仍然是一个堆。然后就可以使用 vector 的成员函数 pop_back() 移除最后一个元素。如果 make_heap() 中用的是自己的比较函数,那么 pop_heap() 的第 3 个参数也需要是这个函数:
std::vector<double> numbers {2.5, 10.0, 3.5, 6.5, 8.0, 12.0, 1.5, 6.0};
std::make_heap(std::begin(numbers),std::end(numbers),std::greater<>());//{1.5 6 2.5 6.5 8 12 3.5 10}
std::pop_heap(std::begin(numbers), std::end(numbers),std:: greater<>());//{2.5 6 3.5 6.5 8 12 10 1.5}
numbers.pop_back();//{2.5 6 3.5 6.5 8 12 10}
同样,为了能够正确执行这个操作,pop_heap() 必须和 make_heap() 使用相同的比较函数。
因为可能会打乱容器中的堆,所以 STL 提供了一个检查序列是否仍然是堆的方法:
if(std::is_heap(std::begin(numbers),std::end(numbers)))
std::cout << "Great! We still have a heap.\n";
else
std::cout << "oh bother! We messed up the heap.\n";
如果元素段是堆,那么 is_heap() 会返回 true。这里是用默认的比较断言 less<> 来检查元素顺序。如果这里使用的是用 greater<> 创建的堆,就会产生错误的结果。为了得到正确的结果,表达式需要写为:
std::is_heap(std::begin(numbers),std::end(numbers),std::greater<>())。
STL 提供的最后一个操作是 sort_heap(),它会将元素段作为堆来排序。如果元素段不是堆,程序会在运行时崩溃。这个函数有以两个迭代器为参数的版本,迭代器指向一个假定的大顶堆(用 less<> 排列),然后将堆中的元素排成降序。下面是一个使用它的示例:
std::vector<double> numbers {2.5, 10.0, 3.5, 6.5, 8.0, 12.0, 1.5, 6.0};
std::make_heap(std::begin(numbers), std::end(numbers));//{12 10 3.5 6.5 8 2.5 1.5 6}
std::sort_heap(std::begin(numbers), std::end(numbers));//{1.5 2.5 3.5 6 6.5 8 10 12}
排序操作的结果不是一个大顶堆,而是一个小顶堆。如图 4 所示,尽管堆并不是全部有序的,但任何全部有序的序列都是堆。
第 2 个版本的 sort_heap() 有第 3 个参数,可以指定一个用来创建堆的断言。如果用断言 greater() 来创建堆,会生成一个小顶堆,对它进行排序会生成一个降序序列。排序后的序列不是小顶堆。下面的代码对此做了展示:
std::vector<double> numbers {2.5, 10.0, 3.5, 6.5, 8.0, 12.0, 1.5, 6.0};
std::make_heap(std::begin(numbers),std::end(numbers),std::greater<>());// {1.5 6 2.5 6.5 8 12 3.5 10}
std::sort_heap(std::begin(numbers), std::end(numbers),std::greater<>());//{12 10 8 6.5 6 3.5 2.5 1.5}