STL container and algorithm usage


Standard Template Library

Six components

  1. Containers: storing data
  2. Algorithm: operation data
  3. Iterator: the algorithm operates on container data through iterator
  4. Imitation function: provide more strategies for algorithms
  5. Adapter: an interface that provides more parameters for the algorithm
  6. Space configurator: dynamically allocate and manage space for algorithms and containers

Important feature: separation of data and operation. The data is managed by the container, and the operation is completed by a specific algorithm. Container and iterator correspond one-to-one

Algorithms are divided into qualitative and non qualitative algorithms:
Qualitative change algorithm: the content of elements in the interval will be changed during the operation, such as copying, replacing, deleting, etc;
Non Prime algorithm: the contents of elements in the interval will not be changed during the operation, such as searching, counting, traversing, searching for extreme values, etc;

string class

string encapsulates char *, which is used to manage strings. It is a char type container
The string class automatically manages the memory allocated by char *. Regardless of memory release and out of bounds, every time a string is copied, the string class is responsible for maintenance

string constructor

string();//Create an empty string, for example: string str;
string(const string& str);//Initialize another string object with one string object
string(const char* s);//Initialize with string s
string(int n, char c);//Initialize with n characters c

string basic assignment operation

string& operator=(const char* s);//char * type string is assigned to the current string
string& operator=(const string &s);//Assign the string s to the current string
string& operator=(char c);//Character assigned to the current string
string& assign(const char *s);//Assign the string s to the current string
string& assign(const char *s, int n);//Assign the first n characters of string s to the current string
string& assign(const string &s);//Assign the string s to the current string
string& assign(int n, char c);//Assign n characters c to the current string
string& assign(const string &s, int start, int n);//Assign s (n characters from start) to the string

string access character operation

char& operator[](int n);//Take characters by [] method
char& at(int n);//Get characters through the at method

string str = "helloc";
str[1] = 'E'; = 'E';

In QT, [] will not throw an exception if it crosses the boundary, and the at method will throw an exception if it crosses the boundary. [] execute str[7] = 'A' to output Hello C normally

string splicing operation

string& operator+=(const string& str);//Overload + = operator
string& operator+=(const char* str);//Overload + = operator
string& operator+=(const char c);//Overload + = operator
string& append(const char *s);//Connect the string s to the end of the current string
string& append(const char *s, int n);//Connect the first n characters of the string s to the end of the current string
string& append(const string &s);//Same as operator + = ()
string& append(const string &s, int pos, int n);//Connect the n characters from pos in the string s to the end of the current string
string& append(int n, char c);//Add n characters c at the end of the current string

Find and replace string

int find(const string& str, int pos = 0) const; //Find the location where str first appears, starting from pos
int find(const char* s, int pos = 0) const; //Find the location where s first appears, starting from pos
int find(const char* s, int pos, int n) const; //Find the first position of the first n characters of s from pos position
int find(const char c, int pos = 0) const; //Find the position where the character c first appears
int rfind(const string& str, int pos = npos) const;//Find the last position of str, starting from pos
int rfind(const char* s, int pos = npos) const;//Find the last position of s, starting from pos
int rfind(const char* s, int pos, int n) const;//Find the last position of the first n characters of s from pos
int rfind(const char c, int pos = 0) const; //Find the last occurrence of the character c
string& replace(int pos, int n, const string& str); //Replace n characters from pos to str
string& replace(int pos, int n, const char* s); //Replace the n characters starting from pos with the string s

string compare operation

//The compare function returns 1 when >.
int compare(const string &s) const;//Compare with string s
int compare(const char *s) const;//Compared with the string s, the > < = = equality operator is overloaded

string substring

string substr(int pos = 0, int n = npos) const;//Returns a string of n characters starting with pos

string insert and delete operations

string& insert(int pos, const char* s); //Insert string
string& insert(int pos, const string& str); //Insert string
string& insert(int pos, int n, char c);//Inserts n characters c at the specified position
string& erase(int pos, int n = npos);//Delete n characters from Pos

String and c-style string conversion

//string to char*
string str = "itcast";
const char* cstr = str.c_str();
//char * to string
char* s = "itcast";
string str(s);

vector container

vector is similar to array, and its biggest feature is the flexible use of space
vector single end operation, can only be added or deleted at one end

v.begin(): get the starting iterator of the container (point to the 0th element)
v.end(): get the end iterator of the container (point to the next position of the last element)

Data structure of vector

Linear continuous space, the two iterators Myfirst and Mylast point to the currently used range, iterator_ Myend points to the end of the whole contiguous memory

In case of need, the actual configured size of the vector may be larger than the required size. Once the capacity is full, the entire vector needs to find a larger continuous space, and then copy the data. And all the original iterators fail

vector constructor

vector<T> v; //The template implementation class is adopted for implementation, and the default constructor is
vector(v.begin(), v.end());//Copy the elements in the v[begin(), end()) interval to itself.
vector(n, elem);//The constructor copies n elem s to itself.
vector(const vector &vec);//Copy constructor.


vector<int>::iterator it = v.begin();//Define an iterator to save the starting iterator
for(; it!=v.end();it++) cout<<*it<<" ";//Traverse container

View vector's memory management mechanism: open up new space
Each time a space is opened: (1) find a new memory; (2) capacity is doubled

vector<int> v;
cout<<v.capacity()<<" "<<v.size()<<endl;//Output 0 0

vector<int>::iterator it;
for(int i=0; i<5; ++i){
    if(it != v.begin()){//The v.begin() iterator changes every time. Description: the memory address of the vector has changed
        cout<<v.capacity()<<endl;//It has been executed for 4 times, outputting 1, 2, 4 and 8 respectively
        it = v.begin();

Preset space


Common assignment operations of vector

assign(beg, end);//Assign the data copy in the [beg, end) interval to itself. Here beg and end are iterators
assign(n, elem);//Assign n elem copies to itself.
vector& operator=(const vector &vec);//Overload the equal sign operator
swap(vec);// Interact vec with its own elements, v2.swap(v1); Swap v1 and V2

Traverse container

printVector(vector<int> &v){
    vector<int>::iterator it = v.begin();
        cout<<*it<<" ";

You can also traverse like an array

vector size operation

resize(int num);//Fill the new location with the default value, which is 0
resize(int num, elem);//Fill the new position with elem
reserve(int len);//Reserved len element lengths, reserved positions are not initialized, and elements are inaccessible
//resize will print a total of num elements, while reserve will only print existing elements

vector data access operation

at(int idx);

vector insertion and deletion

insert(const_iterator pos, int count, elem);//The iterator points to the position pos and inserts count elem s; pos will be moved back
push_back(elem);//Tail insertion
pop_back(elem);//Tail delete
erase(const_iterator start, const_iterator end);//Interval deletion
erase(const_iterator pos);//Delete the element indicated by the iterator
clear();//Delete all;

swap shrink space

resize cannot reduce capacity, only change size or increase capacity

vector<int> v;
vector<int> (v).swap(v);//(v) Represents an anonymous object

v1 is the old object, and vector() is the new anonymous object. Copy construction occurs during the copying of the old object to the anonymous object. Anonymous object copying only copies the interval with data
Then the anonymous object and v exchange, so v only contains the valid data part
The life cycle of the anonymous object is the current statement. After the statement ends, the anonymous object is released

vector container nesting

vector<vector<int>> v;//definition
vector<vector<int>>::iterator it = v.begin();
for(; it!=v.end(); it++){
    vector<int>::iterator mit = (*it).begin();
    for(; mit!=(*it).end(); ++mit){
        cout<<*mit<<" ";

vector and custom types

Overload operator or set friend

Case: sorting custom types

sort(v1.begin(), v1.end(), comparePerson);//comparePerson is a custom collation
class Person{
    friend bool comparePerson(Person ob1, Person ob2);
    friend void printVector(vector<Person> &v);
    int num;
    string name;
    Person(int num, string name){
        this->num = num;
        this->name = name;

bool comparePerson(Person ob1, Person ob2){
    return ob1.num<ob2.num;

void printVector(vector<Person> &v){
    vector<Person>::iterator it = v.begin();
        cout<<(*it).num<<" "<<(*it).name<<endl;

int main(void){
    vector<Person> v;
    v.push_back(Person(3, "lucy"));
    v.push_back(Person(7, "bob"));
    v.push_back(Person(1, "tom"));

    sort(v.begin(), v.end(), comparePerson);//< algorithm > needs to be introduced

    return 0;

deque container

#include <deque>

Difference with vector:
(1) Allow insertion and deletion at both ends (vector s can also be inserted at the head, but the efficiency is extremely poor)
(2) Deque's space is discontinuous and is composed of multiple quantitative continuous spaces. Therefore, deque has no reserve function
(3) The iterator of deque is not an ordinary pointer, and its complexity is higher than that of vector. Vector should be used as much as possible
(4) If necessary, for example, when sorting, deque can be copied to vector, and then copied back after sorting
(5) deque's biggest job is to maintain the integrity of multi segment space and provide random access interfaces. Avoids space reconfiguration, replication and release, but at the cost of complex iterator architecture

deque principle:
Take a map as the central controller
Each map element stores a pointer to a continuous memory space. These contiguous memory spaces are called buffers. The buffer is the storage body of deque

deque constructor

deque <T> deqT;
deque(beg, end);
deque(n, elem);
deque(const deque &deq);

deque assignment operation

assign(beg, end);
assign(n, elem);
deque& operator=(const deque &deq);

deque size operation

deque.resize(num);//Default fill
deque.resize(num, elem);

deque insert and delete

//Double ended operation
push_back(elem);//Tail insertion
push_front(elem);//Head insertion
pop_back();//Tail delete
pop_front();//Head delete
//Insertion: pos beg end are iterators
insert(pos, elem);
insert(pos, n, elem);//Insert n elem s;
insert(pos, beg, end);
erase(beg, end);

deque data access

front();//Return the first element
back();//Returns the last element


There are 5 contestants: contestant ABCDE. 10 judges will score each contestant separately, remove the highest score, remove the lowest score among the judges, and take the average score.

  1. Create five players and put them in the vector
  2. Traverse the vector container, take out each player, execute the for loop, and save the 10 scores into the deque container
  3. sort algorithm sorts the scores in deque container, pop_back pop_front: remove the highest and lowest scores
  4. deque container traverses once, accumulating scores, accumulating scores / d.size()
  5. person.score = average score
using namespace std;

class Person{
    friend void showPerson(vector<Person> &v);
    friend void playGame(vector<Person> &v);
    string name;
    float score;
    Person(string name, float score){
        this->score = score;
        this->name = name;

void showPerson(vector<Person> &v){
    vector<Person>::iterator it = v.begin();
        cout<<(*it).name<<" "<<(*it).score<<endl;

void createPerson(vector<Person>& v){
    string tmpName = "ABCDE";
    for(int i=0; i<5; ++i){
        string name = "player";
        v.push_back(Person(name, 0.0f));

void playGame(vector<Person> &v){
    vector<Person>::iterator it = v.begin();
    for(; it!=v.end(); it++){
        deque<float> d;
        for(int i=0; i<10; ++i){
            d.push_back((float)(rand()%41+60));//1. Input results
        sort(d.begin(), d.end());//2. Sorting
        d.pop_front();//3. Remove the highest and lowest scores
        (*it).score = (float)accumulate(d.begin(), d.end(), 0)/d.size();

int main(void){
    vector<Person> v;

    createPerson(v);//Create player

    return 0;

stack container

//1. stack constructor
stack<T> stkT;
stack(const stack &stk);
//2. stack assignment
stack& operator=(const stack &stk);
//3. stack data access
//4. stack size operation

queue container

queue does not provide traversal function and iterator

//1. queue constructor
queue<T> queT;
queue(const queue &que);
//2. Assignment of queue
queue& operator=(const queue& que);
//2. queue data access
push(elem);//Add element at the end of the queue
pop();//Team leader removal
back();//Returns the last element without deleting it
front();//Returns the first element, but does not delete it
//4. queue size operation

list container

!! The list container is a two-way linked list

The iterator of the list container is a bidirectional iterator, which does not support + n, but supports++

list constructor

list<T> lstT;
list(begin, end);
list(n, elem);
list(const list &lst);

list insert and delete

insert(pos, elem);
insert(pos, n, elem);
insert(pos, beg, end);
erase(beg, end);
remove(elem);//Delete all elements matching elem value

list size operation

resize(num, elem);

list assignment operation

assign(beg, end);
assign(n, elem);
list& operator=(const list &lst);

list data access


list reverse data


set/multiset container

!! All elements will be automatically sorted according to the key value of the element. The element of set is both a key value and a real value

set does not allow the same key value

The characteristics and usage of multiset are completely similar to that of set, but multiset allows duplicate key values

The iterator of set is a read-only iterator, and modification of key value is not allowed

set constructor

set<T> st;
multiset<T> mst;
set(const set &st);

set assignment operation

set& operator=(const set &st);

set size operation


set insert and delete

erase(pos);//pos refers to the iterator, which returns the iterator of the next element
erase(beg, end);
erase(elem);//Delete the element named elem in the container

To modify the collation, you must modify it before inserting. To use a functor
Functor: a class that overloads the function call operator ()

set lookup operation

find(key);//Find out if the key exists, if there is an iterator that returns the key element, if there is no iterator, return set.end();
cout(key);//Find the number of elements of the key, 0 or 1
lower_bound(keyElem);//Return the element of keyElem. If not, return the iterator at the next position
upper_bound(keyElem);//Returns the iterator of the next element of keyelement
//Pay attention to lower_bound,upper_bound,equal_ The iterator returned by range refers to the position, not the size. If there are no qualified ones, return keyElem itself

equal_range returns two iterators, lower_bound and upper_bound, I need to pick it up with pair

//equal_range receives the return value in the form of a pair of groups
pair<set<int>::const_iterator, set<int>::const_iterator> pa;
pa = st.equal_range(6);

Modify collation

//Imitative function: class of overloaded function call calling operator ()
class MyGreater{
    bool operator()(int v1, int v2){
        return v1>v2;

void printSet(set<int, MyGreater> &s){
    set<int, MyGreater>::const_iterator it = s.begin();
    for(; it!=s.end(); it++){
        cout<<*it<<" ";

int main(void){
    set<int, MyGreater> st;
    return 0;

Custom class must modify collation

class MyGreaterPerson;
class Person{
    friend MyGreaterPerson;
    friend void printPersonSet(set<Person, MyGreaterPerson> &st);
    int num;
    string name;
    Person(int num, string name){
        this->name = name;
        this->num = num;
//Custom class must modify collation
class MyGreaterPerson{
    bool operator()(Person ob1, Person ob2){
        return ob1.num < ob2.num;
void printPersonSet(set<Person, MyGreaterPerson> &st){
    set<Person, MyGreaterPerson>::const_iterator it = st.begin();
    for(; it!=st.end(); it++){
        cout<<(*it).num<<" "<<(*it).name<<endl;
int main(void){
    set<Person, MyGreaterPerson> st2;
    st2.insert(Person(9, "lucy"));
    st2.insert(Person(4, "bob"));
    st2.insert(Person(7, "tom"));
    return 0;

pair group

pair<int, string> p1(10086, "move");
pair<int, string> p2 = make_pair(110, "call the police");//recommend

map/multimap container

All elements of the map are pairs. The first element of pair is a key value, and the second element is a real value;
map does not allow the same key value, and the key value is automatically sorted
You cannot modify the key value of the map through the iterator, but you can modify the real value
The only difference between multimap and map is that the key value of multimap can be repeated
Both multimap and map are implemented with red black trees

map constructor

map<T1, T2> mapTT;
map(const map &mp);

map assignment operation

map& operator=(const map &mp);

map size operation


map insert and delete

//Inserting through pair
insert(pair<T1, T2>(elem1, elem2));
insert(make_pair(elem1, elem2));
//By value_type insert
insert(map<T1, T2>::value_type(elem1, elem2));
//Insert by array
mp[key] = value;

erase(beg, end);
erase(keyElem);//Delete the pair group whose key is keyelement in the container

map lookup operation

find(key);//Find whether the key exists. If it exists, return the element iterator. If it does not exist, return map.end();
count(keyElem);//Calculate the number of pairs whose key is keyelement; It is 0 or 1 for a map, and may be greater than 1 for multiple multimap s
equal_range(keyElem);//These three functions have the same meaning as set

!! It is dangerous to use the array method to operate the map. If it does not exist, a key will be automatically created

5 people, 3 departments, required to show people by Department
Idea: vector stores information of 5 people
The multimap stores the pair (Department, vector.iterator) of the group
Find: find the starting iterator of the Department in the multimap
Count: count the number of people in the Department

Usage scenarios of each container

  1. Usage scenario of vector: for example, in the storage of historical operation records of software, we often need to view historical records, such as the last record and the last record, but we do not delete records, because records are descriptions of facts.
  2. Use scenario of deque: for example, in the queuing ticketing system, deque can be used for the storage of queuers, supporting the rapid removal of the head end and the rapid addition of the tail end. Deque supports fast insertion and removal of the head, which is the advantage of deque.
  3. Use scenario of the list: for example, the storage of bus passengers, passengers may get off at any time, and frequent removal and insertion of uncertain location elements are supported.
  4. Use scenario of set: for example, for the storage of personal score records of mobile games, the storage requirements are arranged in order from high scores to low scores.
  5. Usage scenario of map: for example, 100000 users are stored by ID number. If you want to quickly find the corresponding users by ID. Map uses binary tree, which has fast searching efficiency. If it is a vector container, in the worst case, you may have to traverse the entire container to find the user.

STL algorithm


To use a class like a function call, you need to override the () operator

class Print{
    void operator()(char *str){
void test(){
    Print ob;
    ob("helloc!");//Rewrite the () operator to form a form of function call, which is called a function object (functor)

If a function object has one parameter, it is called a unary function object
If a function object has two parameters, it is called a binary function object
If a function object has multiple parameters, it is called a multivariate function object

Function objects usually do not define constructors and destructors, so there will be no problems in construction and destruction
Function objects go beyond the concept of ordinary functions. Function objects can have their own states
Function objects can be compiled inwardly with good performance. It is almost impossible to use function pointers
The function object can adopt template function to make it universal

Predicate (modify rule)

Ordinary functions or functors whose return value type is bool are called predicates
If the predicate has one parameter, it is called a unary predicate
If the predicate has two parameters, it is called a binary predicate

  1. one-element predicate
bool over30(int value){
    return value > 30;
class Over30{
    bool operator()(int value){
        return value > 30;
//General function providing strategy
ret = find_if(v.begin(), v.end(), over30);
//Provision strategy of functor
ret = find_if(v.begin(), v.end(), Over30());//Form an anonymous object
  1. two-place predicate
bool over30(int v1, int v2){
    return v1 > v2;
class Over30{
    bool operator()(int v1, int v2){
        return v1 > v2;
//General function providing strategy
sort(v.begin(), v.end(), over30);
//Provision strategy of functor
sort(v.begin(), v.end(), Over30());

Built in function object

STL has built-in function objects. The objects generated by these functions are the same as general functions

  1. 6 arithmetic class function objects
template<class T> T plus<T>//Additive functor
template<class T> T minus<T>//Subtractive functor
template<class T> T multiplies<T>//Multiplicative functor
template<class T> T divides<T>//Division functor
template<class T> T modulus<T>//Take imitation function
template<class T> T negate<T>//Take inverse function
  1. 6 relational operation class function objects, each of which is binary operation
template<class T> bool equal_to<T>//be equal to
template<class T> bool not_equal_to<T>//Not equal to
template<class T> bool greater<T>//greater than
template<class T> bool greater_equal<T>//Greater than or equal to
template<class T> bool less<T>//less than
template<class T> bool less_equal<T>//Less than or equal to
  1. Logical operation, not is unary, and the rest is binary operation
template<class T> bool logical_and<T>//Logic and
template<class T> bool logical_or<T>//Logical or
template<class T> bool logical_not<T>//Logical non
  1. use
sort(v.begin(), v.end(), greater<int>());//You can rewrite and modify rules by using built-in functions instead of using generic functions
ret = find_if(v.begin(), v.end(), bind2nd(greater<int>(),10));//find_if has only three parameters, so it needs to be bound with bind


function object adaptor

class PrintInt{
    void operator()(int val){
        cout<<val+100<<" ";
for_each(v.begin(), v.end(), PrintInt());//Custom rules required

Further: binding parameters

//2. Public inheritance (binary)_ Function parameter extraction
class PrintInt:public binary_function<int, int, void>{
    //3. Modify operator() with const
    void operator()(int val, int tmp) const{
        cout<<val+tmp<<" ";

void test(){
    vector<int> v;

    //1. bind2nd or bind1st binding parameters
    for_each(v.begin(), v.end(), bind2nd(PrintInt(), 200));

bind1st is bound to the first parameter val, and bind2nd is bound to the second parameter tmp

function pointer adaptor

void myPrintInt(int value, int tmp){
    cout<<value+tmp<<" ";
for_each(v.begin(), v.end(), bind2nd(ptr_fun(myPrintInt), 200));

Member functions as adapters

class Data{
    void printInt(int tmp){
        cout<<value+tmp<<" ";
for_each(v.begin(), v.end(), bind2nd(mem_fun_ref(&Data::printInt), 100));

Reverse Adapter

  1. Negate one yuan
ret = find_if(v.begin(), v.end(), not1(bind2nd(greater<int>(), 30)));//Find the first less than or equal to 30
  1. Binary negation
//lambda expression, supported from c++11
//[] lambda cannot be read or written; [=] lambda reads data; [&] lambda reads and writes data
for_each(v.begin(), v.end(), [&](int val){
    cout<<val<<" ";

sort(v.begin(), v.end(), not2(greater<int>()));//greater requires two parameter comparisons, so use not2

Common algorithms


for_each(iterator beg, iterator end, _callback);


Move the specified container interval element to another container
transform does not allocate memory to the target container, so it needs to allocate memory in advance
@param beg1 source container start iterator
@param end1 source container end iterator
@param beg2 target container start iterator
@param _cakkback callback function or function object
@return "returns the target container iterator."

transform(iterator beg1, iterator end1, iterator beg2, _callback);
int myTransform(int value){
    return value;
transform(v1.begin(), v1.end(), v2.begin(), myTransform);


find(iterator beg, iterator end, iterator, value);//Return v.end() not found


find_if(iterator beg, iterator end, _callback);//Condition search
ret = find_if(v.begin(), v.end(), bind2nd(greater<int>(),30));//Cannot find the returned v.end();


//@Return returns the iterator of the first position of the adjacent element
adjacent_find(iterator beg, iterator end, _callback);


//Binary search must be orderly
bool binary_search(iterator beg, iterator end, value);


//Count the number of occurrences of the element, and return int
count(iterator beg, iterator end, value);


//Find by criteria
count_if(iterator beg, iterator end, _callback);
count_if(v.begin(), v.end(), bind2th(greater<int>(), 10));//Returns the number greater than 10

Common sorting algorithms


//Merge and store two containers into another container
//It is necessary to open up space for the third container in advance
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);//dest target container start iterator


sort(iterator beg, iterator end, _callback);


//Randomly adjust the order of elements within the specified range
random_shuffle(iterator beg, iterator end);
random_shuffle(v.begin(), v.end());


reverse(iterator beg, iterator end);

Common copy replacement algorithms


//Copy: copy the data in one container to another, but will not automatically allocate memory
copy(iterator beg, iterator end, iterator dest.beg);

Copy to terminal

copy(iterator beg, iterator end, ostream_iterator<int>(cout, " "));//'' is a connector


replace(iterator beg, iterator end, oldValue, newValue);
replace(v.begin(). v.end(), 30, 3000);//Replace 30 with 3000


replace_if(iterator beg, iterator end, _callback, newValue);


class Greater30{
    bool operator()(int val){
        return val>30;
replace_if(v.begin(), v.end(), Greater30(), 2000);//In addition to imitation functions, you can also use built-in functions, ordinary functions, etc


swap(container c1, container c2);//Swap two containers

Common arithmetic generation algorithm


//Cumulative summation
accumulate(iterator beg, iterator end, value);//Value means the value after cumulative summation plus value


fill(iterator beg, iterator end, value);//Fill interval value with value

Common set algorithm


//Find the intersection of two sets and save the result in another set
//The set must be an ordered sequence
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest.beg);


v3.resize(min(v1.size(), v2.size()));
vector<int>::iterator ret;
ret = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
copy(v3.begin(), ret, ostream_iterator<int>(cout, " "));


//Set union set
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest.beg);


//Set difference set
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest.beg);

Tags: C++ data structure Algorithm

Posted by Tjeuten on Mon, 12 Sep 2022 02:08:05 +0930