[C++] Summary of Knowledge Points of C++ STL Standard Template Library (Autumn Recruitment)

Article Directory


foreword

Summary of the C++ STL Standard Template Library in the Summary of Autumn Recruitment Notes
The author wrote it with chatgpt, so there may be a way out for some answers (the 3.5 version of GPT has some defects), most of the answers I corrected after I finished writing, there are discrepancies I hope that the students will point out the place.

2023.7.28 first update

The following part of the long code is integrated on the dark horse programmer . Here I have integrated the common functions of each container into a CPP file. If you want to read them separately, you can go directly to station B to search for dark horse programmers to read the corresponding notes.
Dark Horse Programmer Station B Address: Dark Horse Programmer C++

The mind map is as follows:
insert image description here

What are the six components of STL?

STL (Standard Template Library) is an important part of the C++ standard library, which provides a set of general template classes and algorithms for processing various data structures and operations. The six components of STL are as follows:

  1. Containers: Containers are class templates for storing and managing data. STL provides a variety of containers, including vector (vector), linked list (list), double-ended queue (deque), collection (set), mapping (map), etc. Each container has different characteristics and applicable scenarios.
  2. Algorithms: Algorithms are function templates used to perform various operations. STL provides a large number of algorithms, such as sorting, searching, merging, deleting, etc. These algorithms can be applied to various containers, making data processing more convenient and efficient.
  3. Iterators (Iterators): Iterators are used to traverse the elements in the container, similar to the concept of pointers. STL provides several types of iterators, including input iterators, output iterators, forward iterators, bidirectional iterators, and random access iterators. Iterators provide a uniform interface that allows algorithms to operate independently of containers.
  4. Functors: A functor is a class object that overloads the function call operator, which can be called like a function. Algorithms in the STL usually accept functors as parameters, which are used to specify specific operations. STL provides some built-in functors, and you can also customize functors.
  5. Adapters: Adapters are class templates used to convert between different interfaces. STL provides two commonly used adapters: container adapters and iterator adapters. Container adapters can convert the interface of one container to the interface of another container, such as stack (stack) and queue (queue). Iterator adapters can change the behavior of iterators, such as reverse iterators (reverse_iterator) and insert iterators (insert_iterator).
  6. Allocators: Allocators are used to manage the allocation and release of memory. STL provides a default allocator, but also allows users to customize the allocator. Allocators can be specified within a container to control how the container's memory is managed.

These components work together to make the STL a powerful and flexible tool that can greatly simplify the development and maintenance of C++ programs.

Container (container) algorithm (algorithm) iterator (iterator) the relationship between the three?

Container (container), algorithm (algorithm) and iterator (iterator) are three important concepts in C++ STL (Standard Template Library), and there is a close relationship between them.

Containers are objects used to store and organize data, such as vector, , list, mapetc. The container provides various operations, such as insertion, deletion, search and traversal, etc., in order to manage and access the data.

Algorithms are function templates that perform operations on the data in a container, such as sorting, finding, traversing, and so on. Algorithms can be used independently of containers, and can operate on different types of containers, as long as the containers meet the requirements of the algorithm.

The iterator is the bridge between the container and the algorithm , used to traverse the elements in the container. The iterator provides an interface to access the elements of the container, and the data in the container can be accessed and manipulated through the iterator. An iterator can be regarded as a pointer to a container element, and operations such as traversal, access, and modification can be performed.

The relationship between containers, algorithms, and iterators is as follows:

  • Containers provide data storage and organization methods, and algorithms can be used to operate on data in containers.
  • Algorithms are function templates that operate on containers and can be used independently of the container.
  • The iterator is used to traverse the elements in the container and provides an interface to access the elements of the container.

By combining containers, algorithms, and iterators, efficient management and processing of data can be achieved. STL provides a series of containers, algorithms and iterators, making C++ programming more convenient and efficient.

How many types of containers are there? What are they?

Containers in C++ STL can be divided into two types: sequence containers and associative containers. Sequential container : Emphasizes the sorting of values, and each element in the sequential container has a fixed position. Associative container : Binary tree structure, there is no strict physical order relationship between elements.

A sequential container is a container that stores and accesses elements in the order in which they are stored in the container. They provide a series of operations such as inserting an element at the end of the container, inserting an element at a specified position, deleting an element, etc. Common sequential containers include:

  1. vector: Dynamic array, supporting fast random access.
  2. list: Doubly linked list, supports insertion and deletion at any position.
  3. deque: Double-ended queue, which supports insertion and deletion operations at both ends.
  4. array: A fixed-size array whose size is determined at compile time.
  5. forward_list: Singly linked list, which supports insertion and deletion operations at any position.

An associative container is a container that stores and accesses elements by their keys. They use specific data structures (such as red-black trees) to achieve fast lookup of elements. The elements in an associative container are organized according to the sorting method of the key, and the elements can be accessed through the key. Common associative containers include:

  1. set: A collection that stores unique elements, sorted in ascending order of keys.
  2. map: Mapping, storing key-value pairs, sorted in ascending order of keys.
  3. multiset: Multiple collections, storing elements that allow duplicates, sorted in ascending order of keys.
  4. multimap: Multimap, which stores duplicate key-value pairs and sorts them in ascending order of keys.

Sequential containers and associative containers have different functions and usage methods. Choosing the right container depends on specific needs and usage scenarios.

What is the difference between associative and non-associative containers?

Associative containers and non-associative containers are two different types of containers in the C++ standard library.
Associative containers are implemented based on associative data structures, and they provide a mechanism for storing and accessing data based on key-value pairs. Associative containers include std::map, std::set, std::multimap, and std::multiset. The elements in these containers are sorted by key value, and each key value is unique (for std::set and std::multiset, the ordering is based on the value of the element). Associative containers are suitable for scenarios that require access in a specific order or that need to find elements quickly.
Non-associative containers are
implemented based on linear data structures, which provide a mechanism for storing and accessing data in the order in which elements are inserted . Non-associative containers include std::vector, std::list, std::deque, and std::array. Elements in these containers are stored in the order of insertion and can be accessed by index or iterator. Non-associative containers are suitable for scenarios that need to maintain the insertion order of elements or require frequent insertion and deletion operations.

How is the Vector container resized? (Operation when memory is insufficient)

When we resize the vector, the specific steps are as follows:

1) If we want to increase the size of the vector, we can use the resize() function or insert() function to add new elements.

resize() function: You can specify a new size and add default constructed elements at the end. For example, myVector.resize(newSize); will resize the vector to newSize and append default-constructed elements at the end.

insert() function: elements can be inserted at the specified position, thereby increasing the size of the vector. For example, myVector.insert(myIterator, element); will insert the element element at the position specified by myIterator.

2) If we want to reduce the size of the vector, we can use the erase() function to delete elements.

Erase() function: You can delete elements at a specified position or within a specified range, thereby reducing the size of the vector. For example, myVector.erase(myIterator); will delete the element at the position specified by myIterator.
It should be noted that when we resize the vector, it will involve reallocation of memory and movement of elements. The specific steps are as follows:

3) If we want to increase the size of the vector, when the new size exceeds the current capacity, the vector will reallocate a larger memory space. Usually, vector will allocate twice (or more) the current size of the memory space, and then copy the original elements to the new memory space.

4) If we want to reduce the size of the vector, when the new size is smaller than the current capacity, the vector will move the subsequent elements forward to fill the vacancy of the deleted element.
After resizing the vector, the original iterator may become invalid because the position of the element has changed. In order to avoid using invalid iterators, we need to update the iterators in time to point to the correct element positions after resizing the vector.

The difference between iterators and pointers

Container iterators are usually implemented as template classes, which provide pointer-like functionality . The iterator class template defines a set of operators and member functions that make it possible to access elements in a container through an iterator.
Iterators and pointers have some similarities, but they have some differences in concept and usage. :

1) Conceptual difference:

A pointer is a tool for directly accessing memory addresses, which can be used to traverse and manipulate arrays and other data structures.

An iterator is an abstract concept that provides a unified way to access elements in a container (such as an array, linked list, vector, etc.) without knowing the internal implementation details of the container.

2) Flexibility and security:

Pointers have high flexibility and can perform arbitrary address operations and pointer arithmetic operations. But it also increases the possibility of errors, such as accessing out of bounds or freeing invalid memory.

Iterators allow for safer traversal of containers by providing a well-defined set of operations. Iterators usually provide features such as bounds checking and auto-increment to ensure that access is within the scope of the container and reduce the possibility of errors.

3) Abstraction of container:

The iterator abstracts the access method of the container, so that different types of containers can use the same traversal and operation method. This way, the code can be more generic and reusable without having to write specific code for different container types.

Pointers can only be used with certain types of containers, and require knowledge of the internal structure of the container to access them correctly. This will increase the coupling of the code and is not general enough.

4) Iterator classification:

Iterators can be classified according to their functions and access methods, such as forward iterators, bidirectional iterators, random access iterators, etc. These different types of iterators provide different degrees of functionality and performance, and you can choose the appropriate iterator type according to your needs.

While pointers can be used to iterate over data structures such as arrays, iterators provide a higher-level, safer, and more abstract way to access containers. Iterators make the code more general and reusable, and you can choose the appropriate iterator type according to the different characteristics of the container. So while pointers are a powerful tool, iterators are the abstract tool for container access and manipulation commonly used in C++.

Why does iterator fail? What is the solution?

In the process of using iterators, you may encounter the problem of iterator invalidation. Iterator invalidation refers to the fact that the structure of the container changes during the use of the iterator, resulting in the fact that the element pointed to by the iterator does not exist or points to the wrong element . In this case, using an invalid iterator may lead to undefined behavior or even program crash.
Iterator invalidation problems usually appear in the following situations:

1) Insert and delete elements: When we insert or delete elements into the container, it may cause the iterator to become invalid. Inserting an element may cause the position of the element pointed to by the original iterator to change, and deleting an element may cause the element pointed to by the iterator to be deleted.

2) Change the size of the container: When we change the size of the container (such as resizing the vector), it may cause the iterator to become invalid. Changing the size of the container may change the position of the element pointed to by the original iterator.

3) Use the end iterator: When using the iterator to traverse the container, if the iterator continues to use after reaching the end position of the container, the iterator will become invalid. The end iterator represents the next position of the last element in the container, it does not point to a valid element.
To avoid the iterator invalidation problem, we can take the following measures:

1) When inserting and deleting elements, try to use the insertion and deletion functions that return new iterators instead of using the original iterators.

2) When traversing containers, use forward iterators or bidirectional iterators and avoid random access iterators. Because the failure range of forward iterators and bidirectional iterators is relatively small.

3) Before changing the size of the container, copy the elements that need to be kept to another container, and then rebuild the original container.

Talk about the change of the iterator after the erase function of vector is used

The erase() function is a member function provided by the STL container, which is used to delete one or more elements from the container. It takes an iterator as parameter specifying the position of the element to be removed, and returns an iterator pointing to the element after the removed element.

Here are some changes to the iterator after using the erase() function:

Delete a single element:

auto it = vec.erase(position);

After deleting a single element, the iterator it will point to the element after the deleted element. If the last element was removed, it will point to the container's end() iterator.

Delete a range of elements:

auto it = vec.erase(first, last);

After deleting a range of elements, the iterator it will point to the element after the deleted element. If the last element was removed, it will point to the container's end() iterator.
It should be noted that after deleting an element, the original iterator will become invalid and cannot be used any more. If you need to continue traversing the container, you should use the returned new iterator.
Here is an example showing how the iterator changes after the erase() function is used:

#include <iostream>
#include <vector>

int main() {
    
    
    std::vector<int> vec = {
    
    1, 2, 3, 4, 5};

    auto it = vec.erase(vec.begin() + 2); // 删除第三个元素

    std::cout << "After erasing: ";
    for (auto num : vec) {
    
    
        std::cout << num << " ";
    }
    std::cout << std::endl;

    std::cout << "Iterator position: " << *it << std::endl;

    return 0;
}

In the above example, we have used the erase() function to delete the third element in the container vec. Then, we output the contents of the deleted container and print the position of the iterator it.

After the erase function of other containers (such as list, deque, set, map, etc.) is operated, how does the iterator change?

The following is the change of the iterator after the erase() function of some common containers is used:

1)list:

Delete a single element: The erase() function returns an iterator pointing to the element after the deleted element.

Delete elements in a range: The erase() function returns an iterator pointing to the elements after the range to be deleted.

2) therefore:

Delete a single element: The erase() function returns an iterator pointing to the element after the deleted element.

Delete elements in a range: The erase() function returns an iterator pointing to the elements after the range to be deleted.

3) set and map:

Delete a single element: The erase() function returns an iterator pointing to the element after the deleted element.

Delete a range of elements: The erase() function does not return an iterator.

It should be noted that for set and map, when deleting elements in a range, the erase() function does not return an iterator, because these containers are ordered, and after deleting the range, the positions of subsequent elements will be automatically adjusted.
After using the erase() function, be sure to handle the position of the iterator carefully to avoid using invalid iterators.

Vector container uses collection

Yes std::vector, it is a string class in the C++ standard library, and memory is usually allocated on the heap.

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//自定义类
class person{
    
    
public:
    person(int age,float high){
    
    
        this->age=age;
        this->high=high;
        cout<<"对象建立成功!"<<endl;
    }
    void printself(){
    
    
        cout<<"我的年龄是"<<this->age<<endl;
        cout<<"我的身高是"<<this->high<<endl;
    }
    ~person(){
    
    
        cout<<"对象已经被销毁!"<<endl;
    }
private:
    int age;
    float high;
};
//0.vector的三种遍历方式

//vector第三种遍历方式需要传入for_each的打印函数
void print3(int val){
    
    
    cout<<val<<" ";
}
//vector遍历方式1
void printVector(vector<int> v){
    
    
    vector<int>::iterator begin=v.begin();
    vector<int>::iterator end=v.end();
    while(begin!=end){
    
    
        cout<<*begin<<" ";//相当于是解引用
        begin++;
    }
    cout<<endl;
}
// vector遍历方式2(比较常用)
void printVector2(vector<int> v) {
    
    

	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;
}
// vector遍历方式3(使用STL提供的算法)
void printVector3(vector<int> v){
    
    
    for_each(v.begin(),v.end(),print3);
    cout<<endl;
}

int main(){
    
    

    //vector基础操作指令集(以int为例)
    //1.构造方式4种
    vector<int> v1;//无参构造方式
    
	for (int i = 0; i < 10; i++)
	{
    
    
		v1.push_back(i);
	}
	printVector(v1);
    // printVector2(v1);
    // printVector3(v1);
	vector<int> v2(v1.begin(), v1.end());//使用迭代器范围构造
	printVector(v2);

	vector<int> v3(10, 100);//使用重载的构造函数构造
	printVector(v3);
	
	vector<int> v4(v3);//使用拷贝构造函数构造,属于深拷贝(vector的赋值就是深拷贝)
	printVector(v4);

    //2.赋值方式
    vector<int> v5;
    v5=v1;//直接赋值操作,也是深拷贝,和前面vector<int> v4(v3)比较像
    //但是使用拷贝构造函数的方式更为直接和简洁,而赋值操作需要先创建空对象再进行赋值。
 
    vector<int> v6;
    v6.assign(v1.begin(), v1.end());//assign迭代器赋值方式

    vector<int> v7;
    v7.assign(10,100);//assign重载赋值方式
    //3.容器和大小
    if(v1.empty()){
    
    
        cout<<"动态数组1为空!"<<endl;
    }
    else{
    
    
        cout<<"动态数组1不为空!"<<endl;
        cout<<"v1的容量是:"<<v1.capacity()<<endl;//是动态变化的 比容器现在的大小大一些
        cout<<"v1的大小是"<<v1.size()<<endl;//现在的大小
    }
    //4.插入和删除
    v1.push_back(11);//尾插
    v1.pop_back();//尾删
    v1.insert(v1.begin(),100);//指定地方插入1个元素
    v1.insert(v1.begin(),2,100);//指定地方插入多个元素
    // printVector(v1);
    v1.erase(v1.begin());//删除元素
    // printVector(v1);
    v1.erase(v1.begin(),v1.end());//清空方式1
    v1.clear();//清空方式2
    // if(v1.empty()){
    
    
    //     cout<<"动态数组1为空!"<<endl;
    // }
    
    //5.数据存取
    cout<<v1[0]<<" ";//类似数组方法
    cout<<v2.at(0)<<" ";//at方法
    cout<<v3.front()<<" ";//取第一个元素
    cout<<v4.back()<<" ";//取最后一个元素
    cout<<endl;
    //6.容器元素互换
    cout<<"交换前"<<endl;
    printVector(v5);
    printVector(v3);
    v5.swap(v3);
    cout<<"交换后"<<endl;
    printVector(v5);
    printVector(v3);
    //7.预留空间
    v1.reserve(100000);//容器预留len个元素长度,预留位置不初始化,元素不可访问。
    
    return 0;
}

final result:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-VNdg4hJ0-1690345543433) (C:\Users\93701\AppData\Roaming\Typora\typora-user-images\ image-20230720200548128.png)]

Deque container uses collection

Yes std::string, it is a string class in the C++ standard library, and memory is usually allocated on the heap.

#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;

// 0.双端数组的遍历操作
void printDeque(const deque<int>& d) 
//用const是为了防止改变传入对象的状态
//使用&是为了避免重复复制的开销
{
    
    
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
    
    
		cout << *it << " ";

	}
	cout << endl;
}

int main()
{
    
    
	//deque基础操作指令集(以int为例)
    //1.构造方式
    deque<int> d1; //无参构造函数
	for (int i = 0; i < 10; i++)
	{
    
    
		d1.push_back(i);
	}
	printDeque(d1);
	deque<int> d2(d1.begin(),d1.end());//构造函数将[beg, end)区间中的元素拷贝给本身。
	printDeque(d2);

	deque<int>d3(10,100); //构造函数将n个elem拷贝给本身。
	printDeque(d3);

	deque<int>d4 = d3;//拷贝构造函数的一种形式
    //另外一种deque<int>d4(d3)
	printDeque(d4);

    //2.赋值操作
    deque<int> d21;
	for (int i = 0; i < 10; i++)
	{
    
    
		d21.push_back(i);
	}
	printDeque(d21);

	deque<int>d22;
	d22 = d21; //重载等号操作符
	printDeque(d22);

	deque<int>d23;
	d23.assign(d21.begin(), d21.end()); //将[beg, end)区间中的数据拷贝赋值给本身。
	printDeque(d23);

	deque<int>d24;
	d24.assign(10, 100);//将n个elem拷贝赋值给本身。
	printDeque(d24);

    //3.容器大小操作
    deque<int> d31;
	for (int i = 0; i < 10; i++)
	{
    
    
		d31.push_back(i);
	}
	printDeque(d31);

	//判断容器是否为空
	if (d31.empty()) {
    
    
		cout << "d31为空!" << endl;
	}
	else {
    
    
		cout << "d31不为空!" << endl;
		//统计大小
		cout << "d31的大小为:" << d31.size() << endl;
	}

	//重新指定大小
	d31.resize(15, 1);
    //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
	printDeque(d31);

	d31.resize(5);
    //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
	printDeque(d31);

    //4.插入和删除
    deque<int> d41;
	//尾插
	d41.push_back(10);
	d41.push_back(20);
	//头插
	d41.push_front(100);
	d41.push_front(200);

	printDeque(d41);

	//尾删
	d41.pop_back();
	//头删
	d41.pop_front();
	printDeque(d41);
    deque<int> d42;
	d42.push_back(10);
	d42.push_back(20);
	d42.push_front(100);
	d42.push_front(200);
	printDeque(d42);

	d42.insert(d42.begin(), 1000);//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
	printDeque(d42);

	d42.insert(d42.begin(), 2,10000);//在pos位置插入n个elem数据,无返回值。
	printDeque(d42);

	deque<int>d43;
	d43.push_back(1);
	d43.push_back(2);
	d43.push_back(3);

	d43.insert(d43.begin(), d43.begin(), d43.end()); //在pos位置插入[beg,end)区间的数据,无返回值。
	printDeque(d43);
    deque<int> d44;
	d44.push_back(10);
	d44.push_back(20);
	d44.push_front(100);
	d44.push_front(200);
	printDeque(d44);

	d44.erase(d44.begin());
	printDeque(d44); //删除pos位置的数据,返回下一个数据的位置。

	d44.erase(d44.begin(), d44.end()); //删除[beg,end)区间的数据,返回下一个数据的位置。
	d44.clear(); //清空容器的所有数据
	printDeque(d44);

    //5.数据存取
    deque<int> d5;
	d5.push_back(10);
	d5.push_back(20);
	d5.push_front(100);
	d5.push_front(200);

	for (int i = 0; i < d5.size(); i++) {
    
    
		cout << d5[i] << " ";
	}
	cout << endl;


	for (int i = 0; i < d5.size(); i++) {
    
    
		cout << d5.at(i) << " ";
	}
	cout << endl;

	cout << "front:" << d5.front() << endl;

	cout << "back:" << d5.back() << endl;
    
    //6.排序
	deque<int> d6;
	d6.push_back(10);
	d6.push_back(20);
	d6.push_front(100);
	d6.push_front(200);

	printDeque(d6);
	sort(d6.begin(), d6.end());//算法库实现
	printDeque(d6);

    return 0;
}

final result:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-J6IJ6pn5-1690345543433) (C:\Users\93701\AppData\Roaming\Typora\typora-user-images\ image-20230721114404666.png)]

What is the difference between a deque container and a Vector container?

A deque container and a vector container are two different container types with some differences in their internal implementation and usage.

  1. Internal implementation: The deque container is a double-ended array (also known as a double-ended queue), which consists of multiple consecutive buffers, and each buffer can store multiple elements. Vector containers are dynamic arrays that store elements in a contiguous manner in memory.
  2. Insertion and deletion operations: The time complexity of inserting and deleting operations at both ends of the deque container is O(1), while the time complexity of inserting and deleting operations at the end of the vector container is also O(1), but at the head The time complexity of inserting and deleting is O(n), because other elements need to be moved.
  3. Access elements: both deque containers and vector containers support random access, that is, elements can be directly accessed through subscripts. However, since the elements of the vector container are stored contiguously in memory, the random access speed of the vector container is faster. Whether it is a deque container or a vector container, when the subscript of the element is known, the time complexity of accessing the element is O(1) ( the random access mentioned here is for the iterator )
  4. Memory allocation: The deque container uses multiple buffers internally, and the size of each buffer can be dynamically adjusted as needed. The memory space allocated by the vector container is continuous. When the size of the container increases, it may be necessary to reallocate a larger memory space and copy the original elements to the new memory space.

Depending on the specific usage scenarios and requirements, the choice of deque container or vector container will be different. If you need to perform frequent insertion and deletion operations at both ends of the container, and do not need frequent random access to elements, you can choose a deque container. If you need frequent random access to elements, and insert and delete operations at the end, you can choose a vector container.

Tell me about the working principle of the deque container, what is the buffer, and what is the central controller?

Deque (double-ended queue) is a container composed of multiple continuous buffers. Its internal working principle involves the concept of buffers and central controllers.

The buffer is the storage unit inside the deque container, which is a contiguous memory space for storing elements. Each buffer has a fixed size and can hold a certain number of elements. When we insert elements to the head or tail of the deque container, the elements will be stored in the corresponding buffer.

The central controller is the core of the deque container, which contains some important information for managing and controlling the buffer. The controller maintains a pointer to the first and last buffers, and a pointer to the currently active buffer. The central controller also records information such as the number of buffers currently in use, the starting address and size of the buffer.

When we insert elements to the head or tail of the deque container, the central controller will perform corresponding operations according to the current active buffer. If there is enough space in the current active buffer, the insert operation will be performed directly in the current active buffer, and the time complexity is O(1). If the current active buffer is full, the controller creates a new buffer, inserts it at the head or tail, and then inserts elements into the new buffer.

When we delete elements from the head or tail of the deque container, the central controller will also perform corresponding operations according to the current active buffer. If there are still elements in the current active buffer, the deletion operation will be performed directly in the current active buffer, and the time complexity is O(1). If the current active buffer is empty, the central controller will remove it from the deque container and use the first or last buffer as the new active buffer.

Through the management of the central controller, the deque container can implement efficient insertion and deletion operations at both ends, and supports random access to elements. Since the buffer inside the deque container is stored in blocks, it can dynamically adjust the size of the buffer to meet different needs. This makes the deque container more suitable than the vector container in some scenarios.

Is deque.begin() the same as deque.front()?

deque.begin()is a member function that returns an iterator pointing to the first element in the deque container.

deque.front()Also a member function that returns a reference to the first element in the deque container.

(Other containers are also similar answers)

Is the memory allocated by the string container contiguous?

Yes, the memory allocated inside the std::string container is contiguous. In the C++ standard library, std::string usually uses dynamically allocated contiguous memory to store string data.
Specifically, std::string will dynamically allocate a contiguous memory on the heap to store the character data of the string. This means that the characters in the string are sequentially arranged in memory and can be efficiently accessed through pointers and offsets. (Random access)
When the length of std::string grows beyond the currently allocated memory size, it may reallocate a larger memory block and copy the original character data into the new memory. This process is called dynamic memory reallocation, but still maintains the characteristics of contiguous memory.

String container uses collection

Yes std::string, it is a string class in the C++ standard library, and memory is usually allocated on the heap.

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

int main(){
    
    
    //string基础操作指令集(以int为例)
    //1.构造方式4种
    string s1="hello1";//无参构造方式
    cout<<"s1="<<s1<<endl;

    const char* str="hello2";
    string s2(str);//将C语言风格字符串转换为C++风格字符串
    cout<<"s2="<<s2<<endl;
    string s3(s2);//调用拷贝构造函数 深拷贝
    cout<<"s3="<<s3<<endl;

    string s4(10,'a');//使用重载的构造函数
    cout<<"s4="<<s4<<endl;

    //2.赋值方式
    string str1; //char*类型字符串 赋值给当前的字符串
	str1 = "hello world";
	cout << "str1 = " << str1 << endl;

	string str2;
	str2 = str1;//把字符串s赋给当前的字符串
	cout << "str2 = " << str2 << endl;

	string str3;//字符赋值给当前的字符串
	str3 = 'a';
	cout << "str3 = " << str3 << endl;

	string str4; //把字符串s赋给当前字符串
	str4.assign("hello c++");
	cout << "str4 = " << str4 << endl;

	string str5;
	str5.assign("hello c++",5);//把字符串s的前n个字符赋给当前的字符串
	cout << "str5 = " << str5 << endl;

	string str6;
	str6.assign(str5);//把字符串s赋给当前字符串 
    // 和str4的方法很相似 str4输入的参数是const char *s  str6输入的参数是const string &s
	cout << "str6 = " << str6 << endl;

	string str7;
	str7.assign(5, 'x'); //用n个字符c赋给当前字符串
	cout << "str7 = " << str7 << endl;
    //3.字符串拼接
    string str31 = "我";
	str31 += "爱玩游戏";//重载+=操作符string& operator+=(const char* str);
	cout << "str31 = " << str31 << endl;
	str31 += ':';//重载+=操作符 string& operator+=(const char c);
	cout << "str31 = " << str31 << endl;
	string str32 = "LOL DNF";
	str31 += str32;//重载+=操作符`string& operator+=(const string& str);`      
	cout << "str31 = " << str31 << endl;
	string str33 = "I";
	str33.append(" love ");//把字符串s连接到当前字符串结尾
	str33.append("game abcde", 4);//把字符串s的前n个字符连接到当前字符串结尾
	//str3.append(str2);
	str33.append(str32, 4, 3); // 从下标4位置开始 ,截取3个字符,拼接到字符串末尾
	cout << "str33 = " << str33 << endl;
    
    //4.查找和替换
    string str41 = "abcdefgde";
	int pos = str41.find("de");//查找s第一次出现位置,找到了就返回下标(起始位置可以改)
	if (pos == -1)
	{
    
    
		cout << "未找到" << endl;
	}
	else
	{
    
    
		cout << "pos = " << pos << endl;
	}
	pos = str41.rfind("de");//查找s最后一次出现位置
	cout << "pos = " << pos << endl;
    string str42 = "abcdefgde";
	str42.replace(1, 3, "1111");//替换从pos开始的n个字符为字符串s
	cout << "str1 = " << str42 << endl;
    //5.字符串比较
    string s51 = "hello";
	string s52 = "aello";

	int ret = s51.compare(s52);

	if (ret == 0) {
    
    
		cout << "s51 等于 s52" << endl;
	}
	else if (ret > 0)
	{
    
    
		cout << "s51 大于 s52" << endl;
	}
	else
	{
    
    
		cout << "s51 小于 s52" << endl;
	}
    //根据ASCII码表,h的ASCII码值大于a的ASCII码值。
    //如果两个字符串的首字母相同,那么compare()函数会继续比较下一个字符,直到找到不同的字符或者其中一个字符串结束。
    //6.字符存取
    string str61 = "hello world";
	//字符修改
	str61[0] = 'x';//通过[]方式取字符
	str61.at(1) = 'x';  //通过at方法获取字符
	cout << str61 << endl;
    //7.插入和删除
    string str71 = "hello";
	str71.insert(1, "111");//插入字符串
	cout << str71 << endl;

	str71.erase(1, 3);  //从1号位置开始3个字符,删除
	cout << str71 << endl;
    //8.子串
    string str81 = "abcdefg";
	string subStr81 = str81.substr(1, 3);//返回由pos开始的n个字符组成的字符串 pos是第一个参数
	cout << "subStr81 = " << subStr81 << endl;

	string email = "[email protected]";
	int pos2 = email.find("@");
	string username = email.substr(0, pos2);//返回由pos开始的n个字符组成的字符串
	cout << "username: " << username << endl;
    return 0;
}

final result:

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-iuy1GsLg-1690345543434) (C:\Users\93701\AppData\Roaming\Typora\typora-user-images\ image-20230721103712827.png)]

Introduce what data structures stack queues in STL represent

std::stack is an adapter container that uses other containers as the underlying data structure and provides stack (LIFO) functionality. By default, std::deque (double-ended queue) is used as the underlying container of std::stack, but containers such as std::vector or std::list can also be used.

std::stack provides efficient stack operations based on the characteristics of the underlying container. It is suitable for scenarios that require stack functions, such as reverse order output, bracket matching, depth-first search, etc.

std::queue is also an adapter container that uses other containers as the underlying data structure and provides the functionality of a queue (FIFO). By default, std::deque (double-ended queue) is used as the underlying container of std::queue, but containers such as std::list can also be used.

std::queue provides efficient queue operations based on the characteristics of the underlying container. It is suitable for scenarios that require queue functionality, such as breadth-first search, task scheduling, etc.

What is an adapter container

Adapter containers are special containers that use other containers as underlying data structures and provide a set of specific interfaces that allow the underlying container's functionality to adapt to different needs.
The adapter container encapsulates the underlying container, changing its behavior or providing new functionality to meet specific needs. They can simplify the development process, provide a higher level of abstraction, and make the code more flexible and reusable.
In the STL, std::stack and std::queue are examples of adapter containers. They use other containers such as std::deque as the underlying data structure and provide a functional interface to stacks and queues.

For example, a std::vector can be used as the underlying container of a std::stack using the following syntax:

std::stack<int, std::vector<int>> myStack;

This way, myStack will use std::vector as its underlying container instead of the default std::deque.
In the definition of std::stack, there are two template parameters, namely T and Container.

T is the type of elements stored in the stack. In your example, T is specified as int, indicating that the elements stored in the stack are integer types.

Container is the type of the underlying container. In your example, Container is specified as std::vector, which means to use std::vector as the underlying container to realize the function of stack.
It's no wonder that deque has a double-ended list. It turns out that it also implements the functions of stacks and queues.

The stack container uses collections

#include<iostream>
#include<stack>
#include<algorithm>
using namespace std;

int main(){
    
    
    //list基础操作指令集(以int为例)
    stack<int> s;

	//向栈中添加元素,叫做 压栈 入栈
	s.push(10);
	s.push(20);
	s.push(30);

	while (!s.empty()) {
    
    
		//输出栈顶元素
		cout << "栈顶元素为: " << s.top() << endl;
		//弹出栈顶元素
		s.pop();
	}
	cout << "栈的大小为:" << s.size() << endl;
    
    return 0;
}

insert image description here

The queue container uses a collection

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};
int main(){
    
    
    //queue基础操作指令集(以int为例)
    //创建队列
	queue<Person> q;

	//准备数据
	Person p1("唐僧", 30);
	Person p2("孙悟空", 1000);
	Person p3("猪八戒", 900);
	Person p4("沙僧", 800);

	//向队列中添加元素  入队操作
	q.push(p1);
	q.push(p2);
	q.push(p3);
	q.push(p4);

	//队列不提供迭代器,更不支持随机访问	
	while (!q.empty()) {
    
    
		//输出队头元素
		cout << "队头元素-- 姓名: " << q.front().m_Name 
              << " 年龄: "<< q.front().m_Age << endl;
        
		cout << "队尾元素-- 姓名: " << q.back().m_Name  
              << " 年龄: " << q.back().m_Age << endl;
        
		cout << endl;
		//弹出队头元素
		q.pop();
	}

	cout << "队列大小为:" << q.size() << endl;

    return 0;
}

insert image description here

Introduce the list container, which data structure is it implemented by?

List (linked list) is a container in the C++ standard library. It is a doubly linked list that can perform efficient insertion and deletion operations at any position.
Lists are characterized by good performance when inserting and removing elements, whether at the beginning, end, or middle of the container. This is because the structure of the linked list allows insertion and deletion operations in constant time, without the need to reallocate and move elements like arrays. (advantage)
Unlike vector and deque, list does not support random access to elements (disadvantage) because it does not have contiguous memory space like an array. To access the elements in the list, you need to use iterators to traverse. And is a bidirectional iterator. (It can traverse the elements in the container forward and backward, but does not support random access, and needs to start accessing from the first element each time )
list also provides some special operations, such as inserting elements at specified positions, merging two Ordered list, reversed list, etc. These operations are very useful in certain scenarios, such as situations where frequent insertion and deletion of elements is required.
In general, list is a flexible and efficient container, suitable for scenarios that require frequent insertion and deletion of elements without random access. Its operation complexity is O(1), but it needs to traverse the entire linked list when accessing elements, so it may not be as good as vector and deque in terms of performance.

It is implemented by a data structure such as a doubly linked list, which belongs to dynamic storage allocation.

The list container uses a collection

#include<iostream>
#include<list>
#include<algorithm>
using namespace std;

void printList(const list<int>& L) {
    
    

	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;
}
bool myCompare(int val1 , int val2)
{
    
    
	return val1 > val2;
}//自定义排序规则
int main(){
    
    
    //list基础操作指令集(以int为例)
    //1.构造方式
    list<int>L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	printList(L1);

	list<int>L2(L1.begin(),L1.end());
	printList(L2);

	list<int>L3(L2);
	printList(L3);

	list<int>L4(10, 1000);
	printList(L4);
    //2.赋值和交换
    list<int>L21;
	L21.push_back(10);
	L21.push_back(20);
	L21.push_back(30);
	L21.push_back(40);
	printList(L21);

	//赋值
	list<int>L22;
	L22 = L21;
	printList(L22);

	list<int>L23;
	L23.assign(L22.begin(), L22.end());
	printList(L23);

	list<int>L24;
	L24.assign(10, 100);
	printList(L24);
    //交换
    list<int>L25;
	L25.push_back(10);
	L25.push_back(20);
	L25.push_back(30);
	L25.push_back(40);

	list<int>L26;
	L26.assign(10, 100);

	cout << "交换前: " << endl;
	printList(L25);
	printList(L26);

	cout << endl;

	L25.swap(L26);

	cout << "交换后: " << endl;
	printList(L25);
	printList(L26);
    //3.大小操作
    list<int>L31;
	L31.push_back(10);
	L31.push_back(20);
	L31.push_back(30);
	L31.push_back(40);

	if (L31.empty())
	{
    
    
		cout << "L31为空" << endl;
	}
	else
	{
    
    
		cout << "L31不为空" << endl;
		cout << "L31的大小为: " << L31.size() << endl;
	}

	//重新指定大小
	L31.resize(10);
	printList(L31);

	L31.resize(2);
	printList(L31);

    //4.插入和删除
    cout<<"插入和删除演示"<<endl;
    list<int> L41;
	//尾插
	L41.push_back(10);
	L41.push_back(20);
	L41.push_back(30);
	//头插
	L41.push_front(100);
	L41.push_front(200);
	L41.push_front(300);

	printList(L41);

	//尾删
	L41.pop_back();
	printList(L41);

	//头删
	L41.pop_front();
	printList(L41);

	//插入
	list<int>::iterator it = L41.begin();
	L41.insert(++it, 1000);
	printList(L41);

	//删除
	it = L41.begin();
	L41.erase(++it);
	printList(L41);

	//移除
	L41.push_back(10000);
	L41.push_back(10000);
	L41.push_back(10000);
	printList(L41);
	L41.remove(10000);
	printList(L41);
    
    //清空
	L41.clear();
	printList(L41);
    
    //5.数据存取
    list<int>L51;
	L51.push_back(10);
	L51.push_back(20);
	L51.push_back(30);
	L51.push_back(40);

	
	//cout << L51.at(0) << endl;//错误 不支持at访问数据
	//cout << L51[0] << endl; //错误  不支持[]方式访问数据
	cout << "第一个元素为: " << L51.front() << endl;
	cout << "最后一个元素为: " << L51.back() << endl;

	//list容器的迭代器是双向迭代器,不支持随机访问
	list<int>::iterator it11 = L51.begin();
	//it = it + 1;//错误,不可以跳跃访问,即使是+1
    
    //6.反转和排序
    list<int> L6;
	L6.push_back(90);
	L6.push_back(30);
	L6.push_back(20);
	L6.push_back(70);
	printList(L6);

	//反转容器的元素
	L6.reverse();
	printList(L6);

	//排序
	L6.sort(); //默认的排序规则 从小到大
	printList(L6);

	L6.sort(myCompare); //指定规则,从大到小
    //注意,这是类的成员函数sort,和算法库的sort不一样
	printList(L6);
    
    
    return 0;
}

insert image description here

Tell me about the time complexity of common functions of vector deque string list stack queue

Here is a summary of vector, deque, stringand listtheir respective time complexities:

vector:

  • time complexity:
    • Random access: O(1)
    • Insertion and deletion (at positions other than the end): O(n)
    • Insertion and deletion (at the end position): amortized O(1) (approximately O(1))

deque:

  • time complexity:
    • Random access: O(1)
    • Insertion and deletion (at the beginning and end): O(1)
    • Insertion and deletion (in the middle): O(n)

string:

  • time complexity:
    • Random access: O(1)
    • Insertion and deletion (at positions other than the end): O(n)
    • Insertion and deletion (at the end position): amortized O(1)

list:

  • time complexity:
    • Random access: O(n)
    • Insertion and deletion: O(1)

stack:

  • time complexity:
    • Insertion and deletion (on top of the stack): O(1)
    • Access the top element of the stack: O(1)

queue:

  • time complexity:
    • Insertion and deletion (at tail and head): O(1)
    • Access to head element: O(1)

There are several types of iterators in STL

The Standard Template Library (STL) provides various types of iterators to suit the needs of different containers and algorithms. The following are common iterator types in STL:

Input Iterator (Input Iterator): Used for read-only access to elements in the container. It supports incrementing one by one to traverse the container, and the dereference operator ( * ) can be used to get the value of the element.

Output Iterator (Output Iterator): For write-only operations, elements can be inserted into the container. It supports one-by-one incrementing to traverse containers, and the dereference operator can be used to set the value of an element.

Forward Iterator (Forward Iterator): Similar to the input iterator, but supports multiple increment and dereference operations. It can be used to traverse the container multiple times, and can modify the elements in the container during the traversal.

Bidirectional Iterator (Bidirectional Iterator): Similar to the forward iterator, but supports decrement operations. It can traverse the container forward and backward , and can modify the elements in the container during the traversal.

Random Access Iterator (Random Access Iterator): ** is the most powerful iterator type that supports random access, increment, decrement, and arithmetic operations. ** It can access arbitrary elements in the container in constant time and supports pointer-like arithmetic operations.

Containers and algorithms in the STL often specify the required iterator type to ensure that they work properly.

What iterator is used for vector deque string list stack queue set map?

In the STL, different container types use different types of iterators. The following are common container types and their corresponding iterator types:

  • vector: Use Random Access Iterator (Random Access Iterator). Since vector supports constant-time random access, elements can be accessed and modified efficiently using random-access iterators.
  • deque: Use Random Access Iterator (Random Access Iterator). Like vectors, deques also support constant-time random access.
  • string: Use Random Access Iterator (Random Access Iterator). Since strings are essentially sequences of characters, characters can be accessed and modified using random access iterators like arrays.
  • list: Use a bidirectional iterator (Bidirectional Iterator). Since the list is a doubly linked list and only supports bidirectional traversal, the elements in the list can be traversed and modified using a bidirectional iterator.
  • stack: Iterators are not supported. Stack is a last-in-first-out (LIFO) data structure that only allows insertion and deletion at the top of the stack without traversal.
  • queue: Iterators are not supported. Queue is a first-in-first-out (FIFO) data structure that only allows insertion at one end of the queue and deletion at the other end without traversal.
  • set: Use a bidirectional iterator (Bidirectional Iterator). A set is an ordered, non-repetitive collection, and the elements in the set can be traversed and modified using a bidirectional iterator.
  • map: Use a bidirectional iterator (Bidirectional Iterator). A map is a collection of key-value pairs, and the elements in the map can be traversed and modified using a bidirectional iterator.

It should be noted that the above iterator types are only common cases, and the specific implementation may be different. In addition, C++11 introduces more iterator types, such as forward iterator (Forward Iterator) and input iterator (Input Iterator), to support more flexible iterative operations.

Talk about RAII (Resource Acquisition Is Initialization) (resource acquisition is initialization)

RAII (Resource Acquisition Is Initialization) is a programming technique for managing the acquisition and release of resources in C++. It is a management method based on the object life cycle, which ensures the correct release of resources by acquiring resources in the object's constructor and releasing resources in the object's destructor.

Benefits are:

  1. Simplified resource management: By tying resource acquisition and release to the object's lifecycle, the complexity of manually managing resources can be avoided.

  2. Avoid resource leaks: Since the release of resources is carried out in the destructor, even in abnormal situations, resources can be released correctly to avoid resource leaks.

  3. Exception safety: When using RAII to manage resources, even if an exception occurs, the destructor of the object will be called to ensure the correct release of resources and provide better exception safety.

  4. Improve code readability and maintainability: RAII encapsulates resource acquisition and release logic in objects, making the code clearer, more concise and easier to understand

Implementation:

1) Smart pointers are an important tool for implementing RAII. Several smart pointer classes are provided in the C++ standard library, such as std::unique_ptr, , std::shared_ptrand std::weak_ptr. These smart pointer classes encapsulate the pointer and automatically release the resource pointed to by the pointer when the object life cycle ends.

2) Scope-based resource management: In C++, the characteristics of scope can be used to manage resources. Simple RAII can be achieved by creating objects in scope and releasing resources in the object's destructor. For example, use local objects, temporary objects, or function inner classes to manage resources.

3) RAII class: You can create a dedicated RAII class to manage resources. The constructor of this class is responsible for acquiring resources, and the destructor is responsible for releasing resources. By using objects of this RAII class, you can ensure that resources are properly released at the end of the object's lifetime.

4) File handle encapsulation: For file resources, you can create a class that encapsulates the file handle. The constructor of the class opens the file, and the destructor closes the file. In this way, at the end of the object's life cycle, the file handle will be automatically closed.

5) Automatic release of locks: In multithreaded programming, synchronization primitives such as mutexes or semaphores can be used to protect shared resources. By encapsulating the lock acquisition and release operations in the RAII class, you can ensure that the lock will be automatically released when the object life cycle ends, avoiding deadlocks and resource leaks.

6) Appropriate resource management library: There are some third-party libraries that provide more advanced resource management functions, such as and in the Boost scoped_ptrlibrary scoped_array. These libraries provide more RAII classes and tools to simplify the operation of resource management.

Introduce the set container, and talk about the difference between set and multiset,

set is a container in the C++ standard library, and the Chinese name of std::set is a collection . It provides a way to store unique elements, that is, each element is unique in the set and there will be no duplication.
The set container internally uses a Red-Black Tree (Red-Black Tree) data structure to store and sort elements. A red-black tree is a self-balancing binary search tree with good insertion, deletion, and lookup performance.
The characteristics of the set container include:

Uniqueness: The elements in the set are unique, and there will be no duplicate elements .

Automatic sorting: The elements in the set are sorted according to certain sorting rules, and the default is to sort them in ascending order.

Fast insertion, deletion and search: Since the underlying red-black tree is used, the set container provides fast insertion, deletion and search operations with a time complexity of O(logN).

Iterator support: Iterators can be used to traverse the elements in the set container.

Modification is not supported: the elements in the set container cannot be modified. If you need to modify an element, you need to delete it first and then insert it. (Use the member function provided by the set container (such as find) to find the element that needs to be modified. Use the member function provided by the set container (such as erase) to delete the element from the container. Modify the value of the element. Use the member function provided by the set container (eg insert) reinserts the modified element into the container.)

the difference:

Both set and multiset are containers provided in the C++ standard library. **They are both implemented based on red-black trees,** and are used to store a set of elements. The main difference between them is the uniqueness and repetition of elements.

Set container: The elements in the set container are unique, that is, each element can only appear once. If you try to insert an element that already exists, the insert operation will not work. The set container is automatically sorted according to the value of the elements, and can perform insertion, deletion and search operations quickly.

Multiset container: Elements in a multiset container allow repetition , that is, the same element can appear multiple times. The multiset container is sorted according to the value of the elements, and can perform insertion, deletion and search operations quickly. Unlike the set container, the multiset container allows repeated elements to be inserted, and the order of these repeated elements will be maintained according to the sorting rules.

Therefore, the set container is suitable for scenarios that need to store a set of unique elements and perform fast lookup, while the multiset container is suitable for scenarios that need to store a set of elements and allow duplication.
Whether it is set or multiset, they all provide similar interfaces and functions, including operations such as insertion, deletion, search, and iteration. You can choose a suitable container according to your specific needs.

What is the difference between set and multiset, and unorderedset?

std::set, std::multisetand std::unordered_setthese three containers have some differences in implementation and characteristics.

  1. Orderedness: std::setand std::multisetare ordered containers, elements are sorted by their values. std::setOnly unique elements can be stored, while std::multisetduplicate elements are allowed. std::unordered_setIs an unordered container, the order of the elements is indeterminate.
  2. Underlying implementation: std::setand std::multisetis usually implemented using a red-black tree (Red-Black Tree), a data structure that maintains the order of elements. std::unordered_setUsually use a hash table (Hash Table) (hash map
  3. ), this data structure maps elements to buckets through a hash function, enabling fast insertion, deletion, and lookup operations.
  4. Time complexity: std::set, std::multisetand std::unordered_setdiffer in the average time complexity of insertion, deletion, and lookup operations. The average time complexity of std::setand is O(log n), while the average time complexity of is constant time O(1). However, it is possible to reach linear time O(n) in the worst case. (The reason why the first two are slower may be because the tree itself is slower to find)std::multisetstd::unordered_setstd::unordered_set
  5. Element order: std::setand std::multisetare sorted according to the value of the element, so the order of the elements is determined. std::unordered_setThe elements are not sorted, so the order of the elements is undefined.

According to your needs, you can choose the suitable container type. If you need order and uniqueness, you can choose std::setor std::multiset. If you are more concerned about the speed of insertion, deletion and lookup, and don't need orderliness, you can choose std::unordered_set.

Talk about the red-black tree and talk about the underlying principles

The set container uses collections

#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
//自定义数据类型
class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;

};
//set自定义排序方法MyCompare(针对内置数据类型)
//下面用仿函数实现的
class MyCompare 
{
    
    
public:
	bool operator()(int v1, int v2) {
    
    
		return v1 > v2;
	}
};
//set自定义排序方法MyCompare(针对自定义数据类型)
//下面用仿函数实现的
class comparePerson
{
    
    
public:
	bool operator()(const Person& p1, const Person &p2)
	{
    
    
		//按照年龄进行排序  降序
		return p1.m_Age > p2.m_Age;
	}
};
void printSet(set<int> & s)
{
    
    
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)
	{
    
    
		cout << *it << " ";
	}
	cout << endl;
}

int main(){
    
    

    //set基础操作指令集(以int为例)
    //1.构造方式
    set<int> s1;
	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);
	printSet(s1);
	//拷贝构造
	set<int>s2(s1);
	printSet(s2);
	//赋值
	set<int>s3;
	s3 = s2;
	printSet(s3);

    //2.求大小和交换
    set<int> s21;
	
	s21.insert(10);
	s21.insert(30);
	s21.insert(20);
	s21.insert(40);

	if (s21.empty())
	{
    
    
		cout << "s21为空" << endl;
	}
	else
	{
    
    
		cout << "s21不为空" << endl;
		cout << "s21的大小为: " << s21.size() << endl;
	}
    //交换
    set<int> s22;

	s22.insert(10);
	s22.insert(30);
	s22.insert(20);
	s22.insert(40);

	set<int> s23;

	s23.insert(100);
	s23.insert(300);
	s23.insert(200);
	s23.insert(400);

	cout << "交换前" << endl;
	printSet(s22);
	printSet(s23);
	cout << endl;

	cout << "交换后" << endl;
	s22.swap(s23);
	printSet(s22);
	printSet(s23);
    

	//3.插入和删除
	set<int> s31;
	//插入
	s31.insert(10);
	s31.insert(30);
	s31.insert(20);
	s31.insert(40);
	printSet(s31);

	//删除
	s31.erase(s31.begin());
	printSet(s31);

	s31.erase(30);
	printSet(s31);

	//清空
	//s1.erase(s1.begin(), s1.end());
	s31.clear();
	printSet(s31);
	//4.查找和统计
	set<int> s41;
	//插入
	s41.insert(10);
	s41.insert(30);
	s41.insert(20);
	s41.insert(40);
	
	//查找
	set<int>::iterator pos = s41.find(30);

	if (pos != s41.end())
	{
    
    
		cout << "找到了元素 : " << *pos << endl;
	}
	else
	{
    
    
		cout << "未找到元素" << endl;
	}

	//统计
	int num = s41.count(30);
	cout << "num = " << num << endl;
	//5.pair对组创建
	pair<string, int> p(string("Tom"), 20);
	cout << "姓名: " <<  p.first << " 年龄: " << p.second << endl;

	pair<string, int> p2 = make_pair("Jerry", 10);
	cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl;
	//6.排序
	//6.1内置数据类型:
	set<int> s61;
	s61.insert(10);
	s61.insert(40);
	s61.insert(20);
	s61.insert(30);
	s61.insert(50);

	//默认从小到大
	for (set<int>::iterator it = s61.begin(); it != s61.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;

	//指定排序规则
	set<int,MyCompare> s62;
	s62.insert(10);
	s62.insert(40);
	s62.insert(20);
	s62.insert(30);
	s62.insert(50);

	for (set<int, MyCompare>::iterator it = s62.begin(); it != s62.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;
	//6.2自定义类型:
	set<Person, comparePerson> s63;

	Person p1("刘备", 23);
	Person p22("关羽", 27);
	Person p3("张飞", 25);
	Person p4("赵云", 21);

	s63.insert(p1);
	s63.insert(p22);
	s63.insert(p3);
	s63.insert(p4);

	for (set<Person, comparePerson>::iterator it = s63.begin(); it != s63.end(); it++)
	{
    
    
		cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl;
	}

	return 0;
}

insert image description here

Talk about the difference between set container and map container

setand mapare two different types of associative containers in the C++ STL that differ in the following ways:

  1. Storage: setContainers store unique keys, while mapcontainers store key-value pairs, where each key is unique.
  2. Sorting and searching: setThe elements in the container are sorted according to the value of the key, and the key can be used for fast searching. mapThe elements in the container are sorted by the value of the key, and the corresponding value can be quickly looked up by the key.
  3. Insertion and deletion: In setcontainers, insert()functions can be used to insert keys into the container. In mapa container, you can use insert()functions or []operators to insert key-value pairs.
  4. Duplicate Keys: setThe keys in the container are unique and duplicates are not allowed. mapThe key in the container is also unique and no duplication is allowed, but the corresponding value can be updated using the key.
#include <iostream>
#include <set>
#include <map>

int main() {
    
    
    // set容器示例
    std::set<int> mySet;
    mySet.insert(1);
    mySet.insert(2);
    mySet.insert(3);

    for (const auto& element : mySet) {
    
    
        std::cout << "Set Key: " << element << std::endl;
    }

    // map容器示例
    std::map<int, std::string> myMap;
    myMap.insert({
    
    1, "Apple"});
    myMap.insert({
    
    2, "Banana"});
    myMap.insert({
    
    3, "Orange"});

    for (const auto& pair : myMap) {
    
    
        std::cout << "Map Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

Why should the concept of pair pair be considered in the set container?

Because in C++, the set container itself does not support direct storage of key-value pairs (pair).
If you want to store key-value pairs in a set container, you can use std::pair to create an element containing the key and value, and then insert the pair into the set container.
The following is an example showing how to use pair to create a set container containing key-value pairs:

#include <iostream>
#include <set>

int main() {
    
    
    std::set<std::pair<int, std::string>> mySet;

    mySet.insert(std::make_pair(1, "Apple"));
    mySet.insert(std::make_pair(2, "Banana"));
    mySet.insert(std::make_pair(3, "Orange"));

    for (const auto& pair : mySet) {
    
    
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

In this example, we create a set container mySet whose elements are of type std::pair<int, std::string>, which is a combination of an integer key and a string value. Then, we created three key-value pairs using the std::make_pair function and inserted them into the mySet container. Finally, we iterate over the mySet container, and output the key and value for each key-value pair.
It should be noted that since the set container requires unique elements, when we insert a key-value pair into the set container, the set container will sort and deduplicate according to the value of the key (that is, the first element in the pair). This means that two keys are considered equal if they have the same value. :

#include <iostream>
#include <set>

int main() {
    
    
    std::set<std::pair<int, std::string>> mySet;

    mySet.insert(std::make_pair(1, "Apple"));
    mySet.insert(std::make_pair(2, "Banana"));
    mySet.insert(std::make_pair(1, "Orange"));  // 与第一个键值对的键值相同,不会被插入

    for (const auto& pair : mySet) {
    
    
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

In this example, we insert three key-value pairs into the set container mySet. The key of the first key-value pair is 1 and the value is "Apple"; the key of the second key-value pair is 2 and the value is "Banana"; the key of the third key-value pair is also 1 and the value is "Orange" . Since the key of the third key-value pair is the same as the key of the first key-value pair, it will not be inserted into the set container. Therefore, the final output contains only two key-value pairs.
The output is as follows:
Key: 1, Value: Apple
Key: 2, Value: Banana

Note, why not use for(auto pair:myset) directly for the loop here?
1) When we traverse a container, if we just want to read the elements in the container without modifying them, it is better to use const to modify the loop variable. This ensures that we don't accidentally modify elements in the container.
2) Use const auto& pair to access elements in the container by reference, avoiding copy operations and improving efficiency.

Set<> angle brackets can put several parameters

In C++ STL (Standard Template Library), there are usually only two template parameters of set type: element type and comparison function. By default, only the element type needs to be provided, and the comparison function defaults to std::less.
If you want to use a custom comparison function, you can provide it as the second parameter to set. For example:

std::set<int, std::greater<int>> s; // 这会创建一个按照降序排列的 set

Here, std::greater is a function object that tells the set how to sort the elements. But usually, you may not need to provide this parameter, because the default behavior of std::less is to sort in ascending order.
Therefore, the most common thing in angle brackets is a parameter, which is the element type. But if you need, you can also provide a second parameter to define a custom comparison function.

So there is a misunderstanding here. Although two parameters can be placed in the set, it does not mean that he can put key-value pairs.

Talk about the difference between map and mutimap

The Chinese name of map is "mapping"
. When it comes to std::mapand std::multimap, the main difference between them is the uniqueness of the key (key).

  1. Uniqueness of keys: In std::map, each key can only correspond to one value . If you try to insert a key that already exists, it will not insert the new value, but update the value corresponding to the existing key. This makes it std::mapsuitable for situations where unique key-value pairs need to be maintained.
  2. Repeatability of keys: In contrast, in std::multimap, multiple values ​​are allowed for the same key. This means you can insert multiple values ​​with the same key and each value will be preserved. This makes it std::multimapsuitable for situations where key-value pairs need to be stored and duplicate keys are allowed.
#include <iostream>
#include <map>
#include <string>

int main() {
    
    
    std::multimap<int, std::string> myMultiMap;
    myMultiMap.insert(std::make_pair(1, "Apple"));
    myMultiMap.insert(std::make_pair(2, "Banana"));
    myMultiMap.insert(std::make_pair(1, "Orange")); // 相同的键,不同的值
    myMultiMap.insert(std::make_pair(3, "Apple"));

    for (const auto& pair : myMultiMap) {
    
    
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

输出结果:
Key: 1, Value: Apple
Key: 1, Value: Orange
Key: 2, Value: Banana
Key: 3, Value: Apple

  1. Element ordering: std::mapand std::multimapboth can provide ordering. std::mapImplemented using a Red-Black Tree, which is sorted according to the order of the keys. It is also std::multimapimplemented using a red-black tree, but it allows identical keys to exist, so it does not merge identical keys when sorting.

To sum up, std::mapit is suitable for cases where you need unique key-value pairs and sorted by key, and it std::multimapis suitable for cases where you need to allow duplicate keys and sort by key.

How to determine which is the key and which is the value in the map; how to access the key and value?

In std::map and std::multimap, keys and values ​​are represented by the first and second members of the std::pair object.
For std::map and std::multimap iterators, you can use ->first to access keys and ->second to access values.
Here is an example:

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

int main() {
    
    
    std::map<int, std::string> myMap;
    myMap.insert(std::make_pair(1, "Apple"));
    myMap.insert(std::make_pair(2, "Banana"));
    myMap.insert(std::make_pair(3, "Orange"));

    for (const auto& pair : myMap) {
    
    
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

Output result:
Key: 1, Value: Apple
Key: 2, Value: Banana
Key: 3, Value: Orange

In this example, the keys are accessed by pair.first and the values ​​are accessed by pair.second.

The difference between map, mutimap and unordered_map

std::map, std::multimapand std::unordered_mapare three different associative containers in the C++ standard library, and they have the following differences:

  1. Orderliness:
    • std::mapis an ordered associative container, sorted by key, by default in ascending order.
    • std::multimapAlso an ordered associative container that allows insertion of multiple elements with the same key, sorted according to the key.
    • std::unordered_mapIs an unordered associative container, the order of the elements does not depend on the value of the key, but is determined by the hash function .
  2. Underlying implementation:
    • std::mapand are std::multimapusually implemented using red-black trees, which gives them good performance when inserting, deleting, and finding elements.
    • std::unordered_mapImplemented using a hash table , which makes it have constant-time insertion, deletion, and lookup operations in the average case.
  3. Uniqueness of keys:
    • std::mapKeys are required to be unique, and if an element with the same key is inserted, the old element will be replaced by the new one.
    • std::multimapMultiple elements with the same key are allowed to be inserted.
    • std::unordered_mapKeys are required to be unique, and if an element with the same key is inserted, the old element will be replaced by the new one.
  4. Features:
    • std::mapand std::multimapare suitable for scenarios that require ordered access to elements, they provide some functions about the order of elements.
    • std::unordered_mapIt is suitable for scenarios that need to quickly find elements, and its insertion, deletion, and lookup operations have good average performance.

It is important to choose the appropriate associative container based on your needs and specific situation.

The map container uses collections

#include<iostream>
#include<map>
#include<algorithm>
using namespace std;

void printMap(map<int,int>&m)
{
    
    
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)
	{
    
    
		cout << "key = " << it->first << " value = " << it->second << endl;
	}
	cout << endl;
}
//自定义排序的仿函数
class MyCompare {
    
    
public:
	bool operator()(int v1, int v2) {
    
    
		return v1 > v2;
	}
};
int main(){
    
    
    //map基础操作指令集(以int为例)
    //1.构造方式
    map<int,int>m; //默认构造
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));
	printMap(m);

	map<int, int>m2(m); //拷贝构造
	printMap(m2);

	map<int, int>m3;
	m3 = m2; //赋值
	printMap(m3);

    //2.大小和交换
    map<int, int>m21;
	m21.insert(pair<int, int>(1, 10));
	m21.insert(pair<int, int>(2, 20));
	m21.insert(pair<int, int>(3, 30));

	if (m21.empty())
	{
    
    
		cout << "m21为空" << endl;
	}
	else
	{
    
    
		cout << "m21不为空" << endl;
		cout << "m21的大小为: " << m21.size() << endl;
	}

    //交换
    map<int, int>m22;
	m22.insert(pair<int, int>(1, 10));
	m22.insert(pair<int, int>(2, 20));
	m22.insert(pair<int, int>(3, 30));

	map<int, int>m23;
	m23.insert(pair<int, int>(4, 100));
	m23.insert(pair<int, int>(5, 200));
	m23.insert(pair<int, int>(6, 300));

	cout << "交换前" << endl;
	printMap(m22);
	printMap(m23);

	cout << "交换后" << endl;
	m22.swap(m23);
	printMap(m22);
	printMap(m23);
    cout<<"插入删除展示:"<<endl;
    //3.插入和删除
    //插入
	map<int, int> m31;
	//第一种插入方式
	m31.insert(pair<int, int>(1, 10));
	//第二种插入方式
	m31.insert(make_pair(2, 20));
	//第三种插入方式
	m31.insert(map<int, int>::value_type(3, 30));
	//第四种插入方式
	m31[4] = 40; 
	printMap(m31);

	//删除
	m31.erase(m31.begin());
	printMap(m31);

	m31.erase(3);
	printMap(m31);

	//清空
	m31.erase(m31.begin(),m31.end());
	m31.clear();
	printMap(m31);
    //4.查找和统计
    map<int, int>m41; 
	m41.insert(pair<int, int>(1, 10));
	m41.insert(pair<int, int>(2, 20));
	m41.insert(pair<int, int>(3, 30));

	//查找
	map<int, int>::iterator pos = m41.find(3);

	if (pos != m41.end())
	{
    
    
		cout << "找到了元素 key = " << (*pos).first << " value = " << (*pos).second << endl;
	}// 这里用pos->first是等价的
	else
	{
    
    
		cout << "未找到元素" << endl;
	}

	//统计
	int num = m41.count(3);
	cout << "num = " << num << endl;
    
    //5.排序
    map<int, int, MyCompare> m51;

	m51.insert(make_pair(1, 10));
	m51.insert(make_pair(2, 20));
	m51.insert(make_pair(3, 30));
	m51.insert(make_pair(4, 40));
	m51.insert(make_pair(5, 50));

	for (map<int, int, MyCompare>::iterator it = m51.begin(); it != m51.end(); it++) {
    
    
		cout << "key:" << it->first << " value:" << it->second << endl;
	}
    return 0;
}

Talk about back_inserter in STL

std::back_inserter() is a function template that inserts an iterator of elements at the end of the container. It takes a container as an argument and returns an inserter iterator through which elements can be inserted at the end of the container.
Here is the sample code for std::back_inserter():

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 3};
    
    std::vector<int> destination;
    
    std::copy(nums.begin(), nums.end(), std::back_inserter(destination));
    
    for (const auto& num : destination) {
    
    
        std::cout << num << " ";
    }
    
    return 0;
}

In the above example, we have a source container nums which contains some integers. We create an empty target container destination, and then use the std::copy() function to copy the elements in nums to destination.
The std::copy() function accepts three parameters: the start iterator nums.begin() of the source container, the end iterator nums.end() of the source container, and the inserter iterator std::back_inserter( destination)
We use std::back_inserter() as the iterator parameter of the destination container, which ensures that the element is inserted at the end of the destination. (The elements in the source container nums are {1, 2, 3}, so they will be inserted in order at the end of the target container destination. Therefore, the order of the elements in the final destination is also {1, 2, 3}.) Finally,
we Print out the elements in destination, and you can see that they are the same as the elements in nums.
It should be noted that std::back_inserter() is only applicable to containers that support push_back() operations, such as vector and deque. For containers that do not support push_back(), using std::back_inserter() will result in a compilation error.

Talk about some of your commonly used algorithms in STL

When using STL (Standard Template Library), there are some commonly used algorithms that can help us perform various operations. Here are some STL algorithms that I use frequently:

std::sort(): Used to sort the elements in the container, sorted in ascending order by default. You can customize the comparison function to implement different sorting methods.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    5, 2, 8, 1, 9};
    
    std::sort(nums.begin(), nums.end());
    
    for (const auto& num : nums) {
    
    
        std::cout << num << " ";
    }
    
    return 0;
}

std::find(): Finds the element of the specified value in the container and returns an iterator of the element. If not found, returns the container's end() iterator.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    5, 2, 8, 1, 9};
    
    auto it = std::find(nums.begin(), nums.end(), 8);
    
    if (it != nums.end()) {
    
    
        std::cout << "Element found at index: " << std::distance(nums.begin(), it) << std::endl;
        //std::distance() 是一个用于计算两个迭代器之间距离的函数
    } else {
    
    
        std::cout << "Element not found" << std::endl;
    }
    
    return 0;
}

std::copy(): Copy the elements of one container to another container, and you can specify the starting position of the target container.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> source = {
    
    1, 2, 3, 4, 5};
    std::vector<int> destination;
    
    std::copy(source.begin(), source.end(), std::back_inserter(destination));
    
    for (const auto& num : destination) {
    
    
        std::cout << num << " ";
    }
    
    return 0;
}

std::transform(): Transform the elements of a container and store the result in another container, you can specify a conversion function.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 3, 4, 5};
    std::vector<int> squares;
    
    std::transform(nums.begin(), nums.end(), std::back_inserter(squares), [](int num) {
    
    
        return num * num;
    });
    
    for (const auto& square : squares) {
    
    
        std::cout << square << " ";
    }
    
    return 0;
}

std::accumulate(): Accumulate and sum the elements in the container, and you can specify the starting value and operation function.

#include <iostream>
#include <vector>
#include <numeric>

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 3, 4, 5};
    
    int sum = std::accumulate(nums.begin(), nums.end(), 0);
    
    std::cout << "Sum: " << sum << std::endl;
    
    return 0;
}

std::count(): Counts the number of elements in the container equal to the specified value.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 2, 3, 2, 4, 2};
    
    int count = std::count(nums.begin(), nums.end(), 2);
    
    std::cout << "Count: " << count << std::endl;
    
    return 0;
}

std::replace(): Replaces elements in the container equal to the specified value with the new value.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 3, 4, 5};
    
    std::replace(nums.begin(), nums.end(), 3, 10);
    
    for (const auto& num : nums) {
    
    
        std::cout << num << " ";
    }
    
    return 0;
}

std::unique(): Remove consecutive repeated elements in the container, leaving only one.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 2, 3, 3, 3, 4, 4, 4, 4};
    
    auto it = std::unique(nums.begin(), nums.end());
    nums.erase(it, nums.end());
    
    for (const auto& num : nums) {
    
    
        std::cout << num << " ";
    }
    
    return 0;
}

std::for_each(): Perform the specified operation on each element in the container, which can be a function, function object or lambda expression.

#include <iostream>
#include <vector>
#include <algorithm>

void printSquare(int num) {
    
    
    std::cout << num * num << " ";
}

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 3, 4, 5};
    
    std::for_each(nums.begin(), nums.end(), printSquare);
    
    return 0;
}

std::binary_search(): Perform a binary search in an ordered container to determine whether the specified value exists.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> nums = {
    
    1, 2, 3, 4, 5};
    
    bool found = std::binary_search(nums.begin(), nums.end(), 3);
    
    if (found) {
    
    
        std::cout << "Element found" << std::endl;
    } else {
    
    
        std::cout << "Element not found" << std::endl;
    }
    
    return 0;
}

The difference between the member function push_back emplace_back

push_back() and emplace_back() are two functions used to add elements to the end of the container.

push_back(): This is a member function used to add an object to the end of the container. It takes one argument, the value or reference of the element to add. If the container is a dynamic array (such as std::vector), push_back() allocates new memory at the end of the container and copies or moves the elements to the new location.

Example usage:

std::vector<int> myVector;
myVector.push_back(10); // 添加元素 10 到 myVector 的末尾

emplace_back(): This is also a member function used to construct a new object in place at the end of the container. It accepts multiple parameters which will be used in the constructor to construct the new object. Unlike push_back(), emplace_back() does not need to copy or move elements, but constructs new objects directly in the container's memory.

Example usage:

std::vector<std::string> myVector;
myVector.emplace_back("Hello"); // 在 myVector 的末尾构造一个新的 std::string 对象

The advantage of emplace_back() is that it avoids unnecessary copying or moving of objects because it constructs new objects directly in the container's memory. This is especially useful for objects that are expensive to construct, such as custom classes. Note, however, that since emplace_back() constructs objects in-place, the arguments must match the constructor for the type of object stored by the container.
Whether you use push_back() or emplace_back(), you can add elements to the end of the container, which function you choose depends on your needs and situation.

Talk about some member functions with similar but different functions in STL

push_back() vs insert():

push_back() is used to insert an element at the end of the container.

insert() can insert one or more elements at any position in the container.

pop_back() vs erase():

pop_back() is used to remove the last element of the container.

erase() can delete one or more elements in the container, you can specify the position to be deleted or use an iterator to specify the range to be deleted.

front() vs begin():

front() is used to access the first element of the container.

begin() returns an iterator pointing to the first element in the container.

back() vs end():

back() is used to access the last element of the container.

end() returns an iterator pointing to the end of the container (i.e. past the last element).

size() vs empty():

size() returns the number of elements in the container.

empty() checks whether the container is empty, returning a boolean value.

Talk about the difference between the two traversal algorithms of for_each transform

for_each and transform are two different traversal algorithms in STL that differ in their purpose and how they are used.

for_each:

Purpose: for_each is used to perform the specified operation on each element in the container, but does not change the value of the element in the container.

Usage: It accepts a function object (or function pointer) as a parameter, and the function object will be applied to each element in the container. The parameter type of this function object should match the type of the container element. The function object can be an ordinary function, an instance of the function object class, or a lambda expression.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

void printElement(int value) {
    
    
    std::cout << value << " ";
}

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};
    std::for_each(numbers.begin(), numbers.end(), printElement); // 输出:1 2 3 4 5
    return 0;
}

transform:

Purpose: transform is used to perform the specified operation on each element in the container, and store the result in another container, thus generating a new container.

Usage: It takes two iterator ranges and an iterator of the target container as parameters, and a function object. The parameter types of this function object should match the type of the source container element, and the return value type should match the target container element type.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int square(int value) {
    
    
    return value * value;
}

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};
    std::vector<int> squaredNumbers;

    std::transform(numbers.begin(), numbers.end(), std::back_inserter(squaredNumbers), square);

    // 输出原始容器和新容器的内容
    for (int num : numbers) {
    
    
        std::cout << num << " "; // 输出:1 2 3 4 5
    }

    std::cout << std::endl;

    for (int num : squaredNumbers) {
    
    
        std::cout << num << " "; // 输出:1 4 9 16 25
    }

    return 0;
}

To sum up, for_each is used to perform some operation on the elements in the container, and transform is used to perform some operation on the elements in the container and generate a new container.

Talk about the difference between find find_if adjacent_find binary_search count count_if and other search algorithms

find, find_if, adjacent_find, binary_search, count, and count_if are common search algorithms in STL. They are used to find elements in a container or count the number of elements that meet certain conditions. The difference is explained below:

find:

Purpose: find is used to find the element of the specified value in the container.

Usage: It takes as parameters two iterator ranges and the value to look for, and returns an iterator pointing to the element found, or the end of the container if not found.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    auto it = std::find(numbers.begin(), numbers.end(), 3);

    if (it != numbers.end()) {
    
    
        std::cout << "Found: " << *it << std::endl; // 输出:Found: 3
    } else {
    
    
        std::cout << "Not found" << std::endl;
    }

    return 0;
}

find_if:

Purpose: find_if is used to find elements that meet the specified conditions in the container.

Usage: It takes two iterator ranges and a predicate function object as parameters and returns an iterator pointing to the element found, or the end of the container if not found.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

bool isEven(int num) {
    
    
    return num % 2 == 0;
}

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    auto it = std::find_if(numbers.begin(), numbers.end(), isEven);

    if (it != numbers.end()) {
    
    
        std::cout << "Found: " << *it << std::endl; // 输出:Found: 2
    } else {
    
    
        std::cout << "Not found" << std::endl;
    }

    return 0;
}

adjacent_find:

Purpose: adjacent_find is used to find adjacent repeated elements in a container.

Usage: It takes two iterator ranges as arguments and returns an iterator pointing to the first adjacent repeated element found, or to the end of the container if not found.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 2, 3, 4};

    auto it = std::adjacent_find(numbers.begin(), numbers.end());

    if (it != numbers.end()) {
    
    
        std::cout << "Found: " << *it << std::endl; // 输出:Found: 2
    } else {
    
    
        std::cout << "Not found" << std::endl;
    }

    return 0;
}

binary_search:

Purpose: binary_search is used to binary search for an element of a specified value in an ordered container.

How to use it: It takes as parameters two iterator ranges and the value to find, and returns a boolean indicating whether the value was found.

Note: The container must be in order before using binary_search.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    bool found = std::binary_search(numbers.begin(), numbers.end(), 3);

    if (found) {
    
    
        std::cout << "Found" << std::endl;
    } else {
    
    
        std::cout << "Not found" << std::endl; // 输出:Not found
    }

    return 0;
}

count:

Purpose: count is used to count the number of occurrences of the specified value in the container.

Usage: It takes two iterator ranges and the value to count as parameters and returns the number of occurrences of the value.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 2, 3, 2, 4};

    int numTwos = std::count(numbers.begin(), numbers.end(), 2);

    std::cout << "Number of twos: " << numTwos << std::endl; // 输出:Number of twos: 3

    return 0;
}

count_if:

Purpose: count_if is used to calculate the number of elements in the container that meet the specified conditions.

How to use: It accepts two iterator ranges and a predicate function object as parameters, and returns the number of elements that satisfy the condition.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

bool isEven(int num) {
    
    
    return num % 2 == 0;
}

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    int evenCount = std::count_if(numbers.begin(), numbers.end(), isEven);

    std::cout << "Number of even elements: " << evenCount << std::endl; // 输出:Number of even elements: 2

    return 0;
}

Talk about the difference between sort random_shuffle merge reverse and other sorting algorithms

sort, random_shuffle, merge, and reverse are common sorting and rearranging algorithms in STL, which are used to sort and rearrange the elements in the container. The difference is explained below:

sort:

Purpose: sort is used to sort the elements in the container, usually in ascending order (you can also customize the comparison function to achieve other sorting methods).

Usage: It accepts two iterator ranges as parameters and sorts the elements in the container by comparing the elements.

Example:

#include <iostream>
#include <vector>
#include <algorithm>


int main() {
    
    
    std::vector<int> numbers = {
    
    5, 2, 1, 4, 3};

    std::sort(numbers.begin(), numbers.end());

    // 输出排序后的结果:1 2 3 4 5
    for (int num : numbers) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

random_shuffle:

Purpose: random_shuffle is used to randomly rearrange the elements in the container and disrupt the original order.

How to use: It accepts two iterator ranges as parameters, and rearranges the elements in the container through a random algorithm.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    std::random_shuffle(numbers.begin(), numbers.end());

    // 输出随机排列后的结果,例如可能是:3 2 1 5 4
    for (int num : numbers) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

merge:

Purpose: merge is used to merge two sorted containers into a new sorted container.

Usage: It accepts two sorted iterator ranges and an iterator of a target container as arguments, and merges the two input ranges into the target container.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers1 = {
    
    1, 3, 5};
    std::vector<int> numbers2 = {
    
    2, 4, 6};
    std::vector<int> mergedNumbers(6);

    std::merge(numbers1.begin(), numbers1.end(), numbers2.begin(), numbers2.end(), mergedNumbers.begin());

    // 输出合并后的结果:1 2 3 4 5 6
    for (int num : mergedNumbers) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

reverse:

Purpose: reverse is used to reverse the elements in the container, that is, to arrange them in reverse order.

How to use: It accepts two iterator ranges as parameters and sorts the elements in the container in reverse order.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    std::reverse(numbers.begin(), numbers.end());

    // 输出逆序后的结果:5 4 3 2 1
    for (int num : numbers) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

To sum up, these sorting and rearranging algorithms provide different functions in STL, and the appropriate algorithm can be selected according to the requirements to sort, shuffle or reverse the order of the elements in the container.

Talk about the difference between copy, replace, replace_if, swap and other copy replacement algorithms

copy, replace, replace_if, and swap are common copy and replace algorithms in STL, which are used to copy and replace elements in containers. The difference is explained below:

copy:

Purpose: copy is used to copy the elements in one container to another container.

How to use: It accepts two iterator ranges and the iterator of the target container as parameters, and copies the elements in the source container to the target container.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> source = {
    
    1, 2, 3, 4, 5};
    std::vector<int> destination(5);

    std::copy(source.begin(), source.end(), destination.begin());

    // 输出目标容器的内容:1 2 3 4 5
    for (int num : destination) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

replace:

Purpose: replace is used to replace all specified values ​​in a container with another value.

Usage: It takes two iterator ranges and the value to be replaced and the replacement value as parameters, and replaces all specified values ​​with the replacement value.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 2, 3, 4, 2};

    std::replace(numbers.begin(), numbers.end(), 2, 99);

    // 输出替换后的结果:1 99 99 3 4 99
    for (int num : numbers) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

replace_if:

Purpose: replace_if is used to replace all elements in a container that meet a certain condition with another value.

How to use: It accepts two iterator ranges and a predicate function object and replacement value as parameters, and replaces the elements that meet the condition with the replacement value.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

bool isEven(int num) {
    
    
    return num % 2 == 0;
}

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    std::replace_if(numbers.begin(), numbers.end(), isEven, 0);

    // 输出替换偶数后的结果:1 0 3 0 5
    for (int num : numbers) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

swap:

Purpose: swap is used to exchange the values ​​of two containers or elements.

How to use it: It takes two containers or elements as parameters and swaps their values.

Note: swap is efficient for containers because it only swaps pointers and not actual element contents.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    int a = 10;
    int b = 20;

    std::swap(a, b);

    std::cout << "a: " << a << ", b: " << b << std::endl; // 输出:a: 20, b: 10

    return 0;
}

To sum up, these copy and replace algorithms provide different functions in STL, and an appropriate algorithm can be selected to perform element copy and replace operations according to requirements. At the same time, the swap algorithm can also be used to efficiently exchange the values ​​of two containers or elements.

Talk about the difference between accumulate, fill and other arithmetic generation algorithms

accumulate and fill are arithmetic generation algorithms in STL, they are used for numerical computation and filling operations in containers. The difference is explained below:

accumulate:

Purpose: accumulate is used to accumulate the elements in the container or perform cumulative calculations based on some binary operations.

How to use: It accepts two iterator ranges and an initial value as parameters, and uses the specified binary operation to accumulate the elements in the container.

Example:

#include <iostream>
#include <vector>
#include <numeric> // 包含accumulate算法

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);

    std::cout << "Sum: " << sum << std::endl; // 输出:Sum: 15

    return 0;
}

In addition to the simple accumulation in the above example, accumulate can also use custom binary operations to perform cumulative calculations, such as calculating the product of all elements in the container:

#include <iostream>
#include <vector>
#include <numeric> // 包含accumulate算法

int main() {
    
    
    std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};

    int product = std::accumulate(numbers.begin(), numbers.end(), 1, std::multiplies<int>());

    std::cout << "Product: " << product << std::endl; // 输出:Product: 120

    return 0;
}

fill:

Purpose: fill is used to set all elements in the container to the specified value.

How to use it: It takes two iterator ranges and the value to fill as parameters, and sets all elements in the container to the specified value.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> numbers(5);

    std::fill(numbers.begin(), numbers.end(), 42);

    // 输出填充后的结果:42 42 42 42 42
    for (int num : numbers) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

Talk about the difference between set_intersection, set_union, set_difference and other set algorithms

set_intersection, set_union, and set_difference are set algorithms in STL that operate on ordered containers (usually sorted) for intersection, union, and difference operations. The difference is explained below:

set_intersection:

Purpose: set_intersection is used to calculate the intersection of two ordered containers, that is, to find the common elements in the two containers.

Usage: It takes two input containers and an iterator of a target container as parameters, and copies the intersection of the two input containers into the target container.

Note: The premise is that both input containers must be in order.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> set1 = {
    
    1, 2, 3, 4, 5};
    std::vector<int> set2 = {
    
    3, 4, 5, 6, 7};
    std::vector<int> intersection(5);

    std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), intersection.begin());

    // 输出交集的结果:3 4 5
    for (int num : intersection) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

set_union:

Purpose: set_union is used to calculate the union of two ordered containers, that is, to merge all the unique elements in the two containers into a new container.

Usage: It takes two input containers and an iterator of a target container as parameters, and copies the union of the two input containers into the target container.

Note: The premise is that both input containers must be in order.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> set1 = {
    
    1, 2, 3, 4, 5};
    std::vector<int> set2 = {
    
    3, 4, 5, 6, 7};
    std::vector<int> unionSet(10);

    std::set_union(set1.begin(), set1.end(), set2.begin(), set2.end(), unionSet.begin());

    // 输出并集的结果:1 2 3 4 5 6 7
    for (int num : unionSet) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

set_difference:

Purpose: set_difference is used to calculate the difference between two ordered containers, that is, to find elements that are in the first container but not in the second container.

How to use: It accepts two input containers and an iterator of a target container as parameters, and copies the difference of the two input containers into the target container.

Note: The premise is that both input containers must be in order.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> set1 = {
    
    1, 2, 3, 4, 5};
    std::vector<int> set2 = {
    
    3, 4, 5, 6, 7};
    std::vector<int> difference(5);

    std::set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(), difference.begin());

    // 输出差集的结果:1 2
    for (int num : difference) {
    
    
        std::cout << num << " ";
    }

    return 0;
}

To sum up, set_intersection, set_union, and set_difference are algorithms used in STL for set operations on ordered containers. Depending on your specific needs, you can choose an appropriate algorithm to compute the intersection, union, or difference.

Talk about the space configurator of STL

The space configurator of the STL (Standard Template Library) is a component for allocating and managing memory. It is responsible for allocating memory when needed and freeing memory when it is no longer needed to support containers (such as vector, list, map, etc.) and others Dynamic storage requirements of STL components.
The STL space configurator uses a design pattern called "allocator". It allocates and releases memory by providing functions such as allocate() and deallocate(), so that the container can dynamically allocate and release memory space as needed.
STL space allocators usually use low-level memory allocation functions (such as malloc() and free()) to perform the actual memory allocation and deallocation operations. It can also manage memory pools to improve the efficiency of memory allocation.
The space configurator also provides some other functions, such as constructing and destroying objects, getting the size of a memory block, and so on. These features allow containers to store and manage objects in memory, and to construct and destroy objects on demand when needed.
In short, STL's space configurator is a component for allocating and managing memory, which is an important part of STL's dynamic storage requirements. It provides a flexible and extensible way to handle memory allocation and deallocation to support the use of various containers and other STL components.

Take Vector as an example to talk about how the STL space configurator works

The space configurator is a memory management tool in the C++ standard library, which is used to manage dynamically allocated memory blocks. The following takes vector as an example to briefly introduce the working principle of the space configurator:

1) Memory allocation: When vector needs to allocate memory to store elements, it will call the allocate function of the space configurator to request a large enough memory space. The space allocator will use the underlying memory allocation function (such as malloc() or new) to apply for memory from the operating system according to the required memory size.

2) Memory management: The space configurator will manage the allocated memory space, and usually record the status of allocated and unallocated memory blocks. In this way, when the vector needs to increase or decrease elements, the space allocator can quickly locate the available memory block.

3) Memory release: When vector no longer needs a certain memory block, it will call the deallocate function of the space configurator to return the memory block to the space configurator. The space allocator marks the block of memory as available so that it can be reused the next time it is allocated.

It should be noted that the specific implementation of the space configurator may vary between compilers and operating systems. Different space allocators may use different memory management strategies and algorithms to improve the efficiency of memory allocation and release. For example, a space allocator can use memory pools or other techniques to reduce frequent memory allocation and deallocation operations.

Talk about the two-layer structure of the space configurator

The two-level structure of the space configurator means that in STL, the space configurator is designed as a two-level structure, namely the first-level allocator (first-level allocator) and the second-level allocator (second-level allocator). .
1) The first-level configurator is a simple allocator that handles larger memory allocation requests. It uses the global ::operator new() and ::operator delete() functions to perform memory allocation and deallocation. These global functions usually directly call the underlying memory allocation functions (such as malloc() and free()) to complete the actual memory operations.
2) The second-level allocator is a more complex allocator that handles smaller memory allocation requests. It uses a memory pool, which is a large area of ​​memory that has been preallocated. When memory needs to be allocated, the second-level configurator will obtain a memory block of sufficient size from the memory pool and allocate it to the requester. If there are insufficient memory blocks in the memory pool, the second-level allocator calls the first-level allocator to perform a larger memory allocation.
Reason for layering: The division between the first-level configurator and the second-level configurator is to improve the efficiency of memory allocation. Larger memory allocation requests can directly use the first-level configurator, avoiding the management overhead of the memory pool. Smaller memory allocation requests are handled by the second-level configurator, which uses the efficient allocation mechanism of the memory pool to speed up memory allocation and release.
It should be noted that the specific implementation may be different, and different compilers and implementations may adopt different strategies and mechanisms to realize the two-layer structure of the space configurator. But the overall idea is similar, that is, to process memory allocation requests of different sizes through a two-level structure to improve the efficiency of memory allocation.

Talk about the memory pool

Memory pool is a memory management technology, which pre-allocates a continuous memory area when the program starts, and uses this memory area to allocate and release memory during the running of the program. The purpose of the memory pool is to reduce frequent memory allocation and release operations and improve the efficiency of memory management.
The memory pool works as follows:

1) Pre-allocated memory: When the program starts, the memory pool will request a larger memory area from the operating system. This area of ​​memory is usually contiguous and large enough to meet the memory requirements of the program.

2) Allocate memory: When the program needs to allocate memory, the memory pool will obtain a memory block of sufficient size from the pre-allocated memory area and allocate it to the program. This process is usually faster than requesting memory directly from the operating system, because the memory pool does not need to communicate with the operating system as often.

3) Release memory: When a program no longer needs a memory block, it can return the memory block to the memory pool instead of releasing it directly to the operating system. The memory pool will mark this memory block as available so that it can be reused the next time it is allocated.

The benefits of memory pools include:

1) Reduce memory fragmentation: Since the memory pool uses a continuous memory area, it can reduce memory fragmentation. This is because the memory blocks in the memory pool are closely arranged and will not be fragmented.

2) Improve memory allocation efficiency: The memory pool avoids frequent memory allocation operations. By pre-allocating a large memory area, the overhead of memory allocation can be reduced and the efficiency of memory allocation can be improved.

3) Control memory usage: The memory pool can limit the total amount of memory used by the program to avoid memory leaks and over-allocation problems. By preallocating the size of the memory pool, you can control the memory usage of your program.

It should be noted that memory pools are not suitable for all situations. Memory pools may not work well for situations where memory allocations and deallocations are frequent and not fixed in size. In addition, the implementation of the memory pool also needs to consider factors such as thread safety and the complexity of memory management. Therefore, when using memory pools, trade-offs and evaluations need to be made on a case-by-case basis.

Let's talk about functors

When we talk about functors, we're really talking about a special type of object that can be called like a function. A functor is a class or struct that overloads the function call operator operator(). By overloading operator(), the functor can be used as a function in the code, accepting parameters and returning a result.
One of the advantages of functors is their flexibility. Since it is an object, it can be passed as an argument to a function or algorithm, or it can be stored in a container. This allows us to encapsulate custom operation logic in a more intuitive and reusable way, and call it when needed.
Here is a simple example showing how to create and use an additive functor:

class AddFunctor
{
    
    
public:
    int operator()(int a, int b)
    {
    
    
        return a + b;
    }
};

int main()
{
    
    
    AddFunctor add;
    int result = add(3, 4); // 调用仿函数
    // result = 7
    return 0;
}

What is the difference between unary and binary predicates?

Unary and binary predicates are special types of function objects used to perform conditional judgments in algorithms and other functions.
A unary predicate is a function object that takes one argument and returns a boolean value. It is usually used for conditional judgment on a single element. For example, a unary predicate can be used to check whether an integer is even:

struct IsEven
{
    
    
    bool operator()(int num)
    {
    
    
        return num % 2 == 0;
    }
};

int main()
{
    
    
    IsEven isEven;
    bool result = isEven(4); // 调用一元谓词
    // result = true
    return 0;
}

In the above example, IsEven is a unary predicate class that overloads the function call operator operator(), accepts a parameter of type int, and returns a Boolean value indicating whether the number is even or not. By creating an IsEven object isEven, we can use it like calling a function, passing parameters to it, and getting the result.
A binary predicate is a function object that takes two arguments and returns a boolean value. It is usually used for conditional judgment on two elements. For example, a binary predicate can be used to compare the lengths of two strings:

struct CompareLength
{
    
    
    bool operator()(const std::string& str1, const std::string& str2)
    {
    
    
        return str1.length() < str2.length();
    }
};

int main()
{
    
    
    CompareLength compare;
    bool result = compare("apple", "banana"); // 调用二元谓词
    // result = true
    return 0;
}

In the above example, CompareLength is a binary predicate class that overloads the function call operator operator(), takes two parameters of type const std::string& and returns a Boolean value representing the length of the first string Whether the length is less than the length of the second string. By creating the CompareLength object compare, we can use it like calling a function, passing parameters to it, and getting the result.
In summary, a unary predicate is a function object that accepts one parameter and is used for conditional judgment on a single element; a binary predicate is a function object that accepts two parameters and is used for conditional judgment on two elements. They are often used in algorithms and other functions to act on custom conditions.

What is the built-in function object, and the relationship with the functor?

Built-in function object: A built-in function object is a predefined object that can be used like a function, usually defined in the standard library functional . These built-in function objects provide function wrappers for built-in operators, such as arithmetic operators, comparison operators, logical operators, and so on.
In fact, all built-in function objects are functors of a certain type.

Functor: In C++, a functor is a class that has the operator () overloaded in its class definition. Objects of such classes can be called like functions, hence the name "functor". In simple terms, a functor is an object that behaves like a function.

Built-in function objects: These are predefined functors provided by the standard library. These functors are usually defined in header files, including basic arithmetic operations, comparisons, and logical operations.

Therefore, we can say that built-in function objects are a kind of predefined functors, which are a special case of functors. Therefore, the relationship between built-in function objects and functors is the relationship between special cases and general functions, that is, all built-in function objects are functors, but not all functors are built-in function objects, and users can customize functors.

Introducing Arithmetic Functors Relational Functors Logical Functors

Arithmetic Functors: These are functors defined for basic arithmetic operations such as addition, subtraction, multiplication, division, etc. In the C++ standard library, you can find arithmetic functors such as std::plus, std::minus, std::multiplies, and std::divides in header files.

Relational Functors: These functors are used to perform comparison operations such as equal, not equal, less than, greater than, less than or equal to, and greater than or equal to. Likewise, you can find relational functors such as std::equal_to, std::not_equal_to, std::less, std::greater, std::less_equal, and std::greater_equal in the C++ header files.

Logical Functors: These functors are used for logical operations such as logical AND, logical OR, and logical NOT, etc. In the C++ header files, you can find logical functors such as std::logical_and, std::logical_or, and std::logical_not.

Here are some examples of using C++ built-in function objects (arithmetic functors, relational functors, logical functors):

Arithmetic functor:

#include <iostream>
#include <functional>

int main() {
    
    
    std::plus<int> plus_obj;
    std::minus<int> minus_obj;
    std::cout << "10 + 5 = " << plus_obj(10, 5) << std::endl;
    std::cout << "10 - 5 = " << minus_obj(10, 5) << std::endl;
    return 0;
}

Output:
10 + 5 = 15
10 - 5 = 5

Relational functor:

#include <iostream>
#include <functional>

int main() {
    
    
    std::less<int> less_obj;
    std::greater<int> greater_obj;
    std::cout << "Is 10 < 5 ? " << (less_obj(10, 5) ? "true" : "false") << std::endl;
    std::cout << "Is 10 > 5 ? " << (greater_obj(10, 5) ? "true" : "false") << std::endl;
    return 0;
}

Output:
Is 10 < 5 ? false
Is 10 > 5 ? true

Logical functor:

#include <iostream>
#include <functional>

int main() {
    
    
    std::logical_and<bool> and_obj;
    std::logical_or<bool> or_obj;
    std::cout << "true AND false = " << (and_obj(true, false) ? "true" : "false") << std::endl;
    std::cout << "true OR false = " << (or_obj(true, false) ? "true" : "false") << std::endl;
    return 0;
}

输出:
true AND false = false
true OR false = true

Guess you like

Origin blog.csdn.net/weixin_46274756/article/details/131936573