modern_cpp_5-C++ STL container&iterator

Posted by Rippie on Thu, 17 Feb 2022 15:20:52 +0100

C + + container

Basic Usage

Gets the length of the container

Before, we often used sizeof() to obtain the length of the array:

int data[17];
size_t data_size = sizeof(data)/sizeof(data[0]);
printf("Size of array: %zu",data_size);

However, using the size() method of the container can obtain the data length more conveniently:

std::array<int,17> data_{};
std::cout<<"Size of array: "<<data_.size()<<std::endl;

Determine whether the container is empty

No standard way of checking if empty

int full_arr[5] = {1,2,3,4};
printf("Array empty: %d",full_arr[0]==NULL);

empty()

std::vector<int> empty_vec_{};
std::cout<<"Array empty: "<<empty_vec_.empty()<<std::endl;

Get last data

If you do not use a container, getting the last element may be out of bounds:

float f_arr[N] = {1.5,2.3};
printf("Last element : %f",f_arr[3]);

Now you can use the back() method directly

std::array<float,2> f_arr_{1.5,2.3};
cout<<"Last element: "<<f_arr_.back()<<endl;

Clear members

clear()

std::vector<char> letters = {'n','a'};
letters.clear();

Of course, you can also use the std::string type

std::string letters{"nacho"};
letters.clear();

When do I use containers?

Here we should ask a rhetorical question, why not apply to containers? Compared with arrays, containers not only make the program easier to read, but also have more methods, such as size(), empty(), front(), and so on. They also support STL algorithm.

Common containers

std::array

Header file: #include < array >

Create an array container: STD:: array < float, 3 > var = {1.0F, 2.0f, 3.0f} to store a series of variables of the same data type.

example

#include<iostream>
#include<array>

using std::cout;
using std::endl;

int main(){
        std::array<float,3> container_float = {1.1f, 2.2f, 3.3f};
        for(const float & element : container_float){
                cout<<element<<endl;
        }
        cout<<std::boolalpha<<endl;
        cout<<"Array size: "<< container_float.size()<<endl;
        cout<<"Array empty: "<<container_float.empty()<<endl;
        return 0;
}

std::vector

The usage of vectors here is similar to that of the previous arrays. The header file #include < vector > is imported. The implementation of vector is Dynamic table, so elements can be added and deleted:

  • Clear all data: clear
  • Add an element, commonly used in history, VEC push_ Back (value), but VEC is more recommended in C++11 emplace_ Back (value) add element

It is worth noting that vector does not pay attention to the statement of size. We pay more attention to the reserved memory size of a vector, that is, capacity. This is also a direction for us to optimize vector, that is, we should reserve enough memory space to avoid frequent memory applications and releases. The method used is reserve(n), and N is the maximum number of variables expected to be stored.

Case:

Create two vector s. The first one has allocated enough space in advance, and the second one has not allocated enough space. The system will allocate memory automatically

#include<iostream>
#include<vector>

using std::cout;
using std::endl;

int main(){
        const int N = 100;
        std::vector<int> v1; 
        cout<<"v1 has been created\ncapacity:"<<v1.capacity()<<" size:"<<v1.size()<<endl;
        v1.reserve(N);
        cout<<"After v1.capacity() \ncapacity:"<<v1.capacity()<<" size:"<<v1.size()<<endl;
        for(int i=0; i<N ; i++){
                v1.emplace_back(i);
        }
        cout<<"v1 has been filled \ncapacity:"<<v1.capacity()<<" size:"<<v1.size()<<endl;
        // v2
        std::vector<int> v2; 
        cout<<"v2 has been created\ncapacity:"<<v2.capacity()<<" size:"<<v2.size()<<endl;
        for(int i=0; i<N ; i++){
                v2.emplace_back(i);
        }
        cout<<"v2 has been filled \ncapacity:"<<v2.capacity()<<" size:"<<v2.size()<<endl;
        return 0;
}

It is not difficult to find from the results:

  1. The capacity after vector initialization is 0
  2. If memory is not allocated in advance, it may not only waste time due to frequent memory applications, but also waste memory.

application

Open3D::PointCloud

std::map

mapping type

case

#include<iostream>
#include<map>
#include<string>

int main(){
        std::map<int,std::string> students;

        students.emplace(3,"xiaoming"); //[3]
        students.emplace(1,"xiaohong"); //[1]
        students.emplace(2,"xiaohong"); //[2]

        for(const auto& [id,name] : students){
                std::cout<<"id:"<<id<<" name:"<<name<<std::endl;
        }

        std::cout<<"size:"<<students.size()<<std::endl;
        return 0;
}

Output results

It can be seen that the key value pairs here have been reordered.

std::unordered_map

The sorting of this method is realized through the hash table, so the key should be processed by the hash function, such as integer and string. Only change the map to unordered_map. The above cases run in an unordered list, as shown below, and are randomly arranged according to the hash function

At present, all built-in types have implemented hash functions

Iterating over maps

In the previous idea, we can use the following method to iterate the map

for(const auto& kv : m){
    const auto& key = kv.first;
    const auto& value = kv.second;
    // Do work
}

In C++17, we can abbreviate it to the following form

for(const auto& [key,value] : m){
    // Do work
}

Much More about container



C + + iterators

Suppose we need to traverse each element of the output vector, array and list. In the past, we used to create a function for each type, but this function can be realized more concisely by overloading with iterators:

template<typename Iterator>
void print_it(Iterator begin, Iterator end){
    for(Iterator it = begin; it!=end; it++){
        std::cout<<*it<<" ";
    }
    std::cout<<std::endl;
}

It can be seen that the iterator in C + + acts as the glue between the standard library algorithm and the data structure, minimizing the dependence of the algorithm on the operated data structure.

STL is to obtain the elements of the container by using the iterator, * iter points to the element of the current position, + + points to the pointer of the next position, or through = == Compare with <.

Containers also provide different iterators, such as begin, end, and so on.

STL Algorithms

STL has about 80 kinds of algorithms, which are called through #include < algorithm >, so as to avoid making the wheel repeatedly.

The following are the commonly used STL algorithms

  • std::sort(s.begin(),s.end()), in ascending order by default

  • std::find(v.begin(),v.end(),n1)

    auto result = std::find(v.begin(),v.end(),n1);
    if(result!=std::end(v)){
        cout<<"successful";
    }
    else{
        cout<<"false";
    }
    
  • std::fill(v.begin(),v.end(),n1)

  • std::count(v.begin(),v.end(),n1)

  • std::count_if, the count that meets the condition

    inline bool div_by_3(int i){return i%3==0;}
    int main(){
        std::vector<int> v{1,2,3,4,4,5,6,6};
        int result = std::count_if
    }
    
  • std::for_each(begin,end,function_handle)

    #include<vector>
    #include<iostream>
    #include<algorithm>
    
    using std::cout;
    using std::endl;
    
    int main(){
            // Using std::for_each outputs each element of the vector
            std::vector<int> v1{1,2,3,4,5,6};
            //Anonymous function
            auto print = [](const int a){cout<<a<<" "; };
            std::for_each(v1.begin(),v1.end(),print);
            return 0;
    }
    
  • std::all_off() to judge whether all members meet the conditions

    inline bool even(int i){return i%2==0;};
    int main(){
        std::vector<int> v{2,4};
        bool all_is_even = all_of(v.begin(),v.end(),even);
    }
    
  • std::rotate(), rotate the member

    int main () {
        std :: vector <int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        cout << "before rotate: ";
        Print(v);
    
        std :: rotate(v.begin (), v.begin () + 2, v.end ());
        cout << "after rotate: ";
        Print(v);
    }
    //before rotate: 1 2 3 4 5 6 7 8 9 10
    //after rotate: 3 4 5 6 7 8 9 10 1 
    
  • std::transform(), convert the members of one container into the members of another container one by one

    #include<iostream>
    #include<string>
    #include<algorithm>
    
    using std::cout;
    using std::endl;
    
    auto UpperCase(char c){return std::toupper(c);}
    int main(){
            const std::string s("hello");
            std::string S(s);
            std::transform(s.begin(),s.end(),S.begin(),UpperCase);
            cout<<s<<endl;
            cout<<S<<endl;
            return 0;
    }
    // output
    // hello
    // HELLO
    
  • std::accumulate(), which can realize accumulation, multiplication and other operations

    int main () {
     std :: vector <int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
     int sum = std :: accumulate (v.begin (), v.end (), 0);
    
     int product = std :: accumulate (v.begin (),
                                      v.end (),
                                      1,
                                      std :: multiplies ());
    
     cout << "Sum : " << sum << endl;
     cout << "Product: " << product << endl;
     }
    
    // Sum : 55
    // Product: 3628800
    
  • std::min_element()

     int main () {
     std :: vector <int> v{3, 1, 4, 1, 0, 5, 9};
    
     auto result = std :: min_element (v.begin (), v.end ());
     auto min_location = std :: distance(v.begin (), result);
     cout << "min at: " << min_location << endl;
     }
    
    // min at: 4
    
  • std::minmax_element()

    int main () {
     using std :: minmax_element ;
    
     auto v = {3, 9, 1, 4, 2, 5, 9};
     auto [min , max] = minmax_element (begin(v), end(v));
    
     cout << "min = " << *min << endl;
     cout << "max = " << *max << endl;
     }
    
    // min = 1
    // max = 9
    
  • std::clamp()

     int main () {
         // value should be between [kMin ,kMax]
         const double kMax = 1.0F;
         const double kMin = 0.0F;
    
         cout << std :: clamp (0.5 , kMin , kMax) << endl;
         cout << std :: clamp (1.1 , kMin , kMax) << endl;
         cout << std :: clamp (0.1 , kMin , kMax) << endl;
         cout << std :: clamp (-2.1, kMin , kMax) << endl;
     }
    
    // 0.5
    // 1
    // 0.1
    // 0
    
  • std::sample()

     int main () {
         std :: string in = "C++ is cool", out;
         auto rnd_dev = std :: mt19937{ random_device {}() };
         const int kNLetters = 5;
         std :: sample(in.begin (),
         in.end (),
         std :: back_inserter (out),
         kNLetters ,
         rnd_dev);
    
         cout << "from : " << in << endl;
         cout << "sample: " << out << endl;
     }
    // from : C++ is cool
    // sample: C++cl
    

Of this class Slides link and Course address.

Topics: C++ Back-end