Application of STL Library

Keywords: C++

STL Standard Library for C++ Initial Knowledge

STL is an abbreviation for Standard Template Library, translated in Chinese as "Standard Template Library". STL is part of the C++ Standard Library.

Previously, we had a basic understanding of the template templet in C++ and the role of templates. It can be said that C++STL is a powerful set of C++ template classes that provide generic template classes and functions that implement a variety of popular and commonly used algorithms and data structures, such as vectors, chain tables, queues, stacks, etc. It also separates the data structure from the algorithm (templates allow you to implement an algorithm beyond a single data structure).

1. Three Core Components of C++ STL

The core of C++ STL consists of three components:

  1. Containers

    Containers are collections used to manage a class of objects. C++ provides different types of containers, such as deque, list, vector, map, and so on. Different containers are based on different data structures

  2. Algorithms

    Algorithms work on containers. They provide a way to perform various operations, including initialization, sorting, search, and conversion of container content. At the same time, thanks to the C++Templet, the implementation of the algorithm is also container independent.

  3. iterators

    Iterators are used to traverse elements of a collection of objects. These collections may be containers or subsets of containers.

In addition to the three core of STL described above, Algorithms in C++STL have another feature, that is, a step in the algorithm can be implemented by external user parameters (passing in a custom function), which greatly increases the diversity of algorithms.

2. Custom functions and algorithms operate on containers

For example, I believe that everyone has used the sort() sorting algorithm in Algorithms. The last parameter of the sort algorithm is passed in by the user to the comparison function. The sort() algorithm then sorts according to the user-defined comparison method. This allows us to ask the sort algorithm to sort from smallest to largest or from largest to smallest. In addition, if we pass in a class, the sort function will understand our intentions as long as we define in the comparison function how the sort of this class is compared by which member of the class. There is no need to redefine multiple sort algorithms.

Next, I will define my own algorithms and functions to solve sequence transformations (e.g., inversion, square, cube) and pixel transformations (binarization, gray stretching) using containers and iterators.

Custom action functions (analogous to sort()):

// Operations for sequential tables (using the custom operation function MyOperator)
template <class T, class MyOperator>
void transCalc(T a, T& b, int nNum, MyOperator op)
{
    for(int i=0;i<nNum;i++)
    {
        b[i] = op(a[i]);
    }
}

// Operations for chain tables (using the custom operation function MyOperator)
template <class inputIt, class outputIt, class MyOperator>
void transCalcT(inputIt begInput, inputIt endInput, outputIt begOutput, MyOperator op)
{
    for(;begInput!=endInput;begInput++,begOutput++){
        *begOutput = op(*begInput);
    }
}

Custom action templates (analogous to user-defined comparison functions):

// Operations templates are defined here to customize op operations:
//Reverse
template<class T>
T InvT(T a)
{
    return -a;
}
//square
template<class T>
T SqrT(T a)
{
    return a*a;
}

// Class operation template, binary:
// Since binarization requires an incoming threshold in addition to the incoming variable itself, classes are used to define it
template<class T>
class MyThreshold
{
public:
    int threshold;
    // n Default is 128
    MyThreshold(int n=128):threshold(n){}
    // Overload operator'()'to execute customizations once the () incoming parameter is used:
    int operator()(T val)
    {
        return val > threshold;
    }
};




//Compare Template Functions
template<class T>
bool MyCompare(T a, T b)
{
    return a > b;
}
//Custom Compare Template Class
template<class T>
class MyComp
{
public:
    int op;
    // Custom comparison
    MyComp(int n):op(n){}
    bool operator()(T a, T b)
    {
        switch(op){
        case 0:
            return a == b;
            break;
        case 1:
            return a > b;
            break;
        case -1:
            return a < b;
            break;
        }
    }
};

Print functions for easy visualization:

// Print function
template <class T>
void outputCont(string strName, T beg, T end)
{
    cout<<strName;
    for(;beg!=end;beg++){
        cout<<*beg<<"  ";
    }
    cout<<endl;
}

Test sample:

void test_mystl()
{
    const int N = 5;
    vector<int> a = {3,5,4,1,2};
    vector<int> b(5);
    // Reverse
    transCalc(a,b,N,InvT<int>);
    outputCont("Inv a:", b.begin(), b.end());
    // Square
    transCalc(a,b,N,SqrT<int>);
    outputCont("Sqr a:", b.begin(), b.end());
    // Binarization
    transCalc(a,b,N,MyThreshold<int>(2));
    outputCont("Sqr a:", b.begin(), b.end());
    // The sort function uses a custom sort method
    sort(a.begin(), a.end(), MyCompare<int>);
    outputCont("Sort a by max:", a.begin(), a.end());
    // The sort function uses a custom sort class
    sort(a.begin(), a.end(), MyComp<int>(-1));
    outputCont("Sort a by min:", a.begin(), a.end());
}

Test results:

3. Simple digital image processing based on custom functions and operation templates

Image processing for this blog depends on the C++opencv open source algorithm package

First, define an operation template function:

// Operations for images (using the custom operation function MyOperator)
template <class MyOperator>
void transCalc(Mat &src, int w, int h, MyOperator op)
{
    for(int row=0;row<h;row++){
        for(int col=0;col<w;col++){
            // Image Operation
            src.at<uchar>(row, col) = op(src.at<uchar>(row, col));
        }
    }
}

3.1 Image Gray Transform

// Class operation template, gray logarithmic transformation:
// Since binarization requires an incoming threshold in addition to the incoming variable itself, classes are used to define it
template<class T>
class logTransform
{
public:
    int C;
    double Gamma;
    // n Default is 128
    logTransform(int c=1, double gamma = 1.0):C(c),Gamma(gamma){}
    // Overload operator'()'to execute customizations once the () incoming parameter is used:
    int operator()(T val)
    {
        float Val = float(val)/255;
        return 255*C*log(1+Val*(Gamma-1)) / log(Gamma);
    }
};

Test sample:

int main(int argc, char *argv[])
{
    // Open Grayscale Image
    Mat img = cv::imread("C:\\Users\\S.E\\Desktop\\c++\\opencv_qt\\rice.png", 0);
    int w = img.cols;
    int h = img.rows;
    imshow("original", img);
    transCalc(img, w, h ,logTransform<uchar>(1, 0.01));
    imshow("gamma=0.01", img);
    waitKey(0);
    return 0;
}

Test results:

3.2 Image Binarization

// Class operation template, binary:
// Since binarization requires an incoming threshold in addition to the incoming variable itself, classes are used to define it
template<class T>
class MyThreshold
{
public:
    int threshold;
    // n Default is 128
    MyThreshold(int n=128):threshold(n){}
    // Overload operator'()'to execute customizations once the () incoming parameter is used:
    int operator()(T val)
    {
       return (val > threshold)*255;
    }
};

Test sample:

int main(int argc, char *argv[])
{
    // Open Grayscale Image
    Mat img = cv::imread("C:\\Users\\S.E\\Desktop\\c++\\opencv_qt\\rice.png", 0);
    int w = img.cols;
    int h = img.rows;
    imshow("original", img);
    transCalc(img, w, h ,MyThreshold<uchar>(128));
    imshow("Threshold=128", img);
    waitKey(0);
    return 0;

}

Test results:

4. First Identify STL Containers: set Collection

set is a container implemented by the c++stl standard library, which can automatically sort data. Its basic data structure is based on red-black trees, so it is more efficient to insert and delete than regular sequence containers, such as vector s.

Next, we will implement a very simple and super student management "system" based on set for a better understanding.

First, define the basic composition of a student's information and encapsulate it into a class:

// Student Class
class studentInfo
{
public:
    int _strNo;     // School Number
    string _strName; // Full name
    // Constructor
    studentInfo(int strNo, string strName){
        _strNo = strNo;
        _strName =strName;
    }
    // Overload << Output
    friend ostream& operator<<(ostream& os, const studentInfo& info)
    {
        os<<endl<<info._strNo<<" "<<info._strName;
        return os;
    }
    // Override comparison operators (compare school numbers only)
    friend bool operator<(const studentInfo& info1, const studentInfo& info2)
    {
        return info1._strNo<info2._strNo;
    }
};

Next, we define a class for managing student information, store each student information in a set, and define some basic methods for adding, deleting, and checking student information:

It is worth noting that an iterator can be automatically declared using auto when traversing a container with a for loop.

template <class T>
class students
{
public:
    //Store student information
    set<studentInfo> stuSet;
    int stuNum = 0;
    // Constructor
    template <class t>
    students(t stud){
        for(auto it:stud){
            stuSet.insert(it);
            stuNum ++;
        }
    }
    // Add an element
    void add_single(T stu){
        stuSet.insert(stu);
        stuNum ++;
    }
    // Add elements in bulk
    template <class t>
    bool add_batch(t stu){
        for(auto it:stu){
            stuSet.insert(it);
            stuNum ++;
        }
        return true;
    }
    // Delete elements by school number
    bool del(int No){
        for(auto it:stuSet){
            if(it._strNo == No){

                stuSet.erase(it);
                stuNum --;
                return true;
            }
        }
        return true;
    }
    // Delete elements by name
    bool del(string Name){
        for(auto it:stuSet){
            if(!Name.compare(it._strName)){
                stuSet.erase(it);
                stuNum --;
                return true;
            }
        }
        return true;
    }
    // Find names by number
    string searchName(int No){
        for(auto it:stuSet){
            if(it._strNo == No){
                return it._strName;
            }
        }
        return "Not Found";
    }
    // Find a school number by name
    int searchNo(string Name){
        for(auto it:stuSet){
            if(!Name.compare(it._strName)){
                return it._strNo;
            }
        }
        return -1;
    }
    // Modify name according to school number
    void update_no(int No, string afterName){
        for(auto it:stuSet){
            if(it._strNo==No){
                stuSet.erase(it);
                break;
            }
        }
        stuSet.insert(studentInfo(No, afterName));
    }

};

It is worth noting that in an implementation where set involves finding and deleting, if we have deleted a piece of data from the set, we should either return or break out of the lookup loop directly. This is because after deleting a piece of data, the tree structure in the set changes because of the tree traversal mechanism inside the set. This prevents erase from traversing back and forth from that starting point, so if the next data happens to be above the parent node of the current data, traversing back and forth will result in a field pointer error. That is:

... ...
        for(auto it:stuSet){
            if(it._strNo==No){
                stuSet.erase(it);
                break;
            }
        }
... ...

Test sample:

void testStuSet()
{
    vector<studentInfo> stu1;
    stu1.push_back(studentInfo(11, "haotianY"));
    stu1.push_back(studentInfo(13, "jinyuG"));
    stu1.push_back(studentInfo(47, "jiawenL"));
    vector<studentInfo> stu2;
    stu2.push_back(studentInfo(41, "zhaohongH"));
    stu2.push_back(studentInfo(19, "chenxiS"));
    stu2.push_back(studentInfo(22, "yihaoW"));
    stu2.push_back(studentInfo(49, "haohaoW"));


    students<studentInfo> stuSet(stu1);
    // increase
    stuSet.add_batch(stu2);
    stuSet.add_single(studentInfo(22, "xiangdongX"));
    // Delete
    stuSet.del(22);
    // change
    stuSet.update_no(47, "xiuwenL");  // Modify name according to school number
    // check
    cout<<stuSet.searchName(49)<<endl;      // Find names by number
    cout<<stuSet.searchNo("haohaoW")<<endl; // Find a school number by name
    outputCont("student Set:\n", stuSet.stuSet.begin(), stuSet.stuSet.end());
}

Test results:

5. First Identify STL Containers: Map (Associated Container)

A map is an associated container that provides one-to-one data processing capabilities (the first is called a key and the second is called a value). The values of different keys can be the same. Because of this feature, it provides a fast path programmatically when we process one-to-one data. Also, like set, map is automatically sorted inside.

Map: Enter a string, use map to count the number of occurrences of each character, and output the characters and their corresponding times.

//Enter a string and use map to count the number of occurrences of each character and output the character and its corresponding number of occurrences
void countStr(string str){
    map<char, int> count;
    for(int i = 0;i<str.length();i++){
        // Assign 1 if first occurrence
        if(count.find(str[i])==count.end()){
            count[str[i]] = 1;
        }
        // Otherwise++.
        else{count[str[i]]++;}
    }
    // Print the number of occurrences of each word
    for(auto i:count){
        cout<<i.first<<": "<<i.second<<"  ";
    }
}

Test sample:

int main()
{
    countStr("iuewfhjsdbfkrhedjskjloisgjipjdzzsvdfgkzlkjbhgvdvjj");
    return 0;
}

Test results:

epilogue

Based on the template templet introduced in the previous experiment and STL Library in C++ standard library, this experiment implements a custom algorithm and functions and performs a simple digital image processing. At the same time, through two examples of student information management and counting the number of occurrences of each letter in the string, the basic usage of set and map is preliminarily understood. So far, c++ has formally opened the door to me, revealing a little insignificant light.
More C++ based applications will be presented as my settings for this semester.

Posted by GYK on Thu, 25 Nov 2021 09:10:42 -0800