C++ STL: Sequential Containers

1. Overview of Sequential Containers

The so-called sequential container is to store data of a specified type (such as int, double, etc.) in a linear arrangement (similar to the storage method of an ordinary array). Values ​​are sorted by size.

Sequential containers mainly include the following types of containers:

  • array<T,N> (array container): It means that N elements of type T can be stored, and it is a container provided by C++ itself. Once such a container is established, its length is fixed, which means that elements cannot be added or deleted, only the value of an element can be changed.
  • vector<T> (vector container): used to store elements of type T, it is a variable-length sequence container, that is, when the storage space is insufficient, it will automatically apply for more memory. Using this container, the efficiency of adding or deleting elements at the end is the highest (time complexity is O(1) constant order), and inserting or deleting elements at other positions is less efficient (time complexity is O(n) linear order, where n is the number of elements in the container).
  • deque<T> (double-ended queue container): very similar to vector, the difference is that using this container is not only efficient for inserting and deleting elements at the end, but also efficient for inserting or deleting elements at the head, and the time complexity is O(1) constant order, but to insert or delete elements at a certain position in the container, the time complexity is O(n) linear order.
  • list<T> (linked list container): It is a variable-length sequence composed of T-type elements. It organizes elements in the form of a doubly linked list. Elements can be efficiently added or deleted anywhere in this sequence (time complexity The degree is constant order O(1)), but the speed of accessing any element in the container is slower than the first three containers, because the list must be accessed from the first element or the last element, and needs to move along the linked list, until the desired element is reached.
  • forward_list<T> (forward linked list container): It is very similar to the list container, except that it organizes elements in the form of a single linked list, and its internal elements can only be accessed from the first element, which is faster and more efficient than the linked list container Memory-efficient containers.

Note that in fact, in addition to this, stack and queue are also sequential containers in essence, but they are all modified on the basis of deque containers, and it is usually more customary to call them container adapters.

The following diagram illustrates the various sequential containers and the differences between them:

insert image description here


2. Array

2.1 Overview

The Array container is a new sequential container in the C++ 11 standard. Simply understood, it is based on the C++ ordinary array, adding some member functions and global functions. In use, it is safer than ordinary arrays, and the efficiency has not deteriorated. Unlike other containers, the size of the array container is fixed and cannot be expanded or contracted dynamically, which means that the size of the container cannot be changed by adding or removing elements during the use of the container. It only allows access or Replace stored elements.

The array container is defined in the <array> header file in the form of a class template and is located in the namespace std, as follows:

namespace std{
    template <typename T, size_t N>
    class array;
}

Therefore, before using the container, the <array> header file needs to be introduced in the code, and the std command space is used by default, as follows:

#include <array>
using namespace std;

In the array<T,N> class template, T is used to indicate the specific data type stored in the container, and N is used to indicate the size of the container. It should be noted that N here must be a constant and cannot be represented by variables.

  1. There are many ways to initialize the array container. The following code shows how to create an array container with 10 elements of double type:
std::array<double, 10> values;

Thus, an array container named values ​​is created, which contains 10 floating-point elements. However, since the values ​​of these 10 elements are not explicitly specified, the values ​​of each element in the container created in this way are indeterminate (the array container will not be initialized by default).

  1. All elements can be initialized to 0 or an equivalent value to the default element type by creating an array container as follows:
std::array<double, 10> values {};

Using this statement, all elements in the container will be initialized to 0.0.

  1. Of course, when creating an instance of an array container, elements can also be initialized like regular arrays:
std::array<double, 10> values {0.5,1.0,1.5,,2.0};

2.2 Member functions

member function Function
begin() Returns a random-access iterator pointing to the first element in the container.
end() Returns a random access iterator pointing to a position after the last element of the container, usually used in conjunction with begin().
rbegin() Returns a random access iterator pointing to the last element.
rend() Returns a random-access iterator pointing to the position one previous to the first element.
cbegin() It has the same function as begin(), except that the const attribute is added on top of it, and cannot be used to modify elements.
a few() It has the same function as end(), except that the const attribute is added on top of it, and cannot be used to modify elements.
crbegin() It has the same function as rbegin(), except that the const attribute is added on top of it, and cannot be used to modify elements.
crend() It has the same function as rend(), except that the const attribute is added on top of it, and cannot be used to modify elements.
size() Returns the number of elements currently in the container, which is always equal to the second template parameter N that initializes the array class.
max_size() Returns the maximum number of elements the container can hold, always equal to the second template parameter N that initializes the array class.
empty() Judging whether the container is empty is the same as the judgment condition of size()==0, but its efficiency may be faster.
at(n) Returns a reference to the element at position n in the container. This function automatically checks whether n is in the valid range, and throws an out_of_range exception if not.
front() Returns a direct reference to the first element in the container, this function does not work with an empty array container.
back() Returns the direct application of the last element in the container, this function does not work on an empty array container.
data() Returns a pointer to the first element of the container. Using this pointer, similar functions such as copying all elements in the container can be realized.
fill(val) Assign the value val to each element in the container.
array1.swap(array2) Swaps all elements in the array1 and array2 containers, but only if they have the same length and type.

In addition, the C++ 11 standard library has added two new functions, begin() and end(). Unlike the begin() and end() member functions contained in the array container, the two functions provided by the standard library The operation object of the function can be a container or an ordinary array. When the operation object is a container, it has the same function as the begin() and end() member functions contained in the container; if the operation object is an ordinary array, the begin() function returns a pointer to the first element of the array, Similarly end() returns a pointer to a position after the last element in the array (note that it is not the last element).

In addition, the get() global function is overloaded in the <array> header file. The function of this overloaded function is to access the specified element in the container and return the reference of the element.

2.3 Iterators

The STL provides random access iterators for array containers, which are the most powerful iterators. In the template class of the array container, the member functions related to the random access iterator are shown in the following table:

member function Function
begin() Returns a forward iterator pointing to the first element in the container; if it is a const type container, this function returns a constant forward iterator.
end() Returns a forward iterator pointing to a position after the last element of the container; if it is a const type container, this function returns a constant forward iterator. This function is usually used with begin().
rbegin() Returns a reverse iterator pointing to the last element; if it is a const type container, this function returns a constant reverse iterator.
rend() Returns a reverse iterator pointing to one position before the first element. If it is a const type container, the function returns a constant reverse iterator. This function is usually used with rbegin().
cbegin() The function is similar to begin(), except that the returned iterator type is a constant forward iterator, which cannot be used to modify elements.
a few() It has the same function as end(), except that the returned iterator type is a constant forward iterator, which cannot be used to modify elements.
crbegin() It has the same function as rbegin(), except that the returned iterator type is a constant reverse iterator, which cannot be used to modify elements.
crend() It has the same function as rend(), except that the returned iterator type is a constant reverse iterator, which cannot be used to modify elements.

In addition, the begin() and end() functions newly added in the C++11 standard are also related to iterators when the operation object is an array container, and their functions are respectively the same as the begin() and end() members in the above table The functions are the same.

2.3.1 begin()/end() and cbegin()/cend()

  1. The begin() and end() member functions in the array container template class return forward iterators, which point to the positions of "first element" and "tail element+1" respectively. In actual use, we can use them to initialize the container or traverse the elements in the container.

For example, iterators can be used explicitly in a loop to initialize the values ​​of the values ​​container:

#include <iostream>
#include <array>
using namespace std;

int main() {
    array<int, 5> values;
    int h = 1;
    auto first = values.begin();
    auto last = values.end();
    // 初始化 values 容器为{1,2,3,4,5}
    while (first != last) {
        *first = h;
        ++first;
        ++h;
    }
  
    first = values.begin();
    while (first != last) {
        cout << *first << " "; // 输出结果为:1 2 3 4 5
        ++first;
    }
    return 0; 
}

It can be seen that the iterator object is returned by the member functions begin() and end() of the array object. We can use iterator objects like ordinary pointers. For example, in the code, after saving the element values, use the prefix ++ operator to increment first. When first is equal to end, all elements are set and the loop ends.

At the same time, you can also use the global begin() and end() functions to obtain iterators from the container, because when the operation object is an array container, they are common to the begin()/end() member functions. So in the above code, first and last can also be defined as follows:

auto first = std::begin(values);
auto last = std::end (values);

In this way, a segment of elements in a container can be specified by an iterator, which gives us the possibility to use algorithms on them.

It should be noted that there are not only array containers in STL. When an iterator points to a specific element in the container, they do not retain any information about the container itself, so we cannot judge from the iterator whether it points to an array container or Points to the vector container.

  1. In addition, the array template class also provides cbegin() and cend() member functions. The only difference between them and begin()/end() is that the former returns a forward iterator of const type, which means That is, there are iterators returned by the cbegin() and cend() member functions, which can be used to traverse the elements in the container, and can also access the elements, but the stored elements cannot be modified. For example the following code:
#include <iostream>
#include <array>
using namespace std;

int main() {
    array<int, 5> values{ 1,2,3,4,5 };
    int h = 1;
    auto first = values.cbegin();
    auto last = values.cend();

    // 由于 *first 为 const 类型,不能用来修改元素,因此下面的代码错误
    // *first = 10;

    // 遍历容器并输出容器中所有元素
    while (first != last) {
        // 可以使用 const 类型迭代器访问元素
        cout << *first << " "; // 输出结果为:1 2 3 4 5
        ++first;
    }
    return 0;
}

2.3.2 rbegin()/rend() 和 crbegin()/crend()

The array template class also provides rbegin()/rend() and crbegin()/crend() member functions, and each pair of them can get a random access iterator pointing to the last element and the previous position of the first element respectively, They are also called reverse iterators. That is to say, when using a reverse iterator for ++OR --operation, ++it means that the iterator moves one bit to the left, --and it means that the iterator moves one bit to the right, that is, the functions of these two operators are "exchanged" .

Reverse iterators are used to process elements in reverse order. For example:

#include <iostream>
#include <array>
using namespace std;

int main() {
    array<int, 5> values;
    int h = 1;
    auto first = values.rbegin();
    auto last = values.rend();
    // 初始化 values 容器为 {5,4,3,2,1}
    while (first != last) {
        *first = h;
        ++first;
        ++h;
    }

    // 逆序遍历容器,并输出各个元素
    first = values.rbegin();
    while (first != last) {
        cout << *first << " "; // 输出结果为:1 2 3 4 5
        ++first;
    }
    return 0;
}

It can be seen that, starting from the last element, the loop not only completes the initialization of the container, but also traverses and outputs all the elements in the container. The end iterator points to the position before the first element, so when first points to the first element and +1, the loop ends.

Of course, in the above program, we can also use the for loop:

for (auto first = values.rbegin(); first != values.rend(); ++first) {
    cout << *first << " ";
}

The only difference between crbegin()/crend() combination and rbegin()/rend() combination is that the iterator returned by the former is of const type, that is, it cannot be used to modify the elements in the container. is exactly the same as the latter.

2.4 Accessing elements

2.4.1 Accessing a single element in an array container

  1. First, you can directly access and use the elements in the container through the container name [], which is the same as the way of accessing elements in C++ standard arrays, for example:
values[4] = values[3] + 2.O*values[1];

In this line of code, the value of the fifth element is assigned the value of the expression on the right. It should be noted that using the above method, since no bounds check is performed, even if an out-of-bounds index value is used to access or store elements, it will not be detected.

In order to effectively avoid out-of-bounds access, you can use the at() member function provided by the array container, for example:

values.at(4) = values.at(3) + 2.O*values.at(1);

This line of code achieves the same function as the previous line, but when the index passed to at() is an out-of-range value, the program will throw an std::out_of_rangeexception. Therefore, when you need to access a specified element in the container, it is recommended to use at(), unless it is determined that the index is not out of bounds.

We may have such a question, that is, why the array container does not implement the function of bounds checking when overloading the [] operator? The answer is simple: because of performance. If you check the index value every time you access an element, there will undoubtedly be a lot of overhead. This overhead can be avoided when there is no possibility of out-of-bounds access.

  1. In addition, the array container also provides the get<n> template function, which is an auxiliary function that can get the nth element of the container. It should be noted that in this template function, the actual parameter of the parameter must be a constant expression that can be determined at compile time, so it cannot be a loop variable. That is, it can only access the element specified by the template parameter, which is checked by the compiler at compile time.

The following code shows how to use the get<n> template function:

#include <iostream>
#include <array>
#include <string>
using namespace std;

int main() {
    array<string, 5> words{ "one","two","three","four","five" };
    cout << get<3>(words) << endl; // 输出 words[3],即 "four"
    //cout << get<6>(words) << endl; // 越界,会发生编译错误
    return 0;
}
  1. In addition, the array container also provides the data() member function, which can be called to get a pointer to the first element of the container. Through this pointer, we can get each element in the container, for example:
#include <iostream>
#include <array>
using namespace std;

int main() {
    array<int, 5> words{1,2,3,4,5};
    cout << *( words.data()+1); // 输出结果为:2
    return 0;
}

2.4.2 Access multiple elements in the array container

  1. We know that the size() function provided by the array container can return the number of elements in the container (the return value of the function is of type size_t), so we can extract the elements in the container one by one and calculate their sum as follows:
double total = 0;
for(size_t i = 0 ; i < values.size() ; ++i)
{
    total += values[i];
}

The existence of the size() function provides the array container with an advantage over standard arrays, namely the ability to know how many elements it contains. Moreover, functions that accept array containers as parameters only need to call the member function size() of the container to get the number of elements. In addition, by calling the empty() member function of the array container, you can know whether there is an element in the container (if there is no element in the container, this function returns true), as shown below:

if(values.empty())
    std::cout << "The container has no elements.\n";
else
    std::cout << "The container has " << values.size() << " elements.\n";

However, empty array containers are rarely created because when an array container is generated, its number of elements is fixed and cannot be changed, so the only way to generate an empty array container is to specify the second parameter of the template as 0, but this is unlikely to happen.

The reason why the array container provides the empty() member function is because: For other containers whose elements are variable or whose elements can be deleted (such as vector, deque, etc.), the mechanism when they use empty() is the same, so for They provide a consistent operation.

  1. In addition to using size(), range-based loops can be used for any container that can use iterators, so it is easier to calculate the sum of all elements in the container, such as:
double total = 0;
for(const auto& value : values)
    total += value;

Guess you like

Origin blog.csdn.net/crossoverpptx/article/details/131612529