The range for loop is a new feature of C++11. It can be used in various C++ containers such as vector and map. It is very convenient to traverse all elements of the container. I found the range for loop in the process of manually implementing the array class. lower level mechanism. Writing a blog is just a record of my own learning. If it happens to help a beginner understand the mechanism, it is an honor for Bencaicai.
Manually implement the array class reference Manually simulate the array class in C++ stl - Harris_ayaka's blog - CSDN blog
grammar
Go directly to the code:
#include<iostream> #include<vector> int main() { std::vector<int> nums{ 1,2,3,4,5,6,7,8 }; for (int num : nums) { std::cout << num << std::endl; } }
Output result:
It can be seen that this writing method is very convenient when you need to traverse the container completely and do not need to use subscripts.
use mechanism
If you want to modify the value of a variable, use a reference type:
for (int& num : nums) {//declared as a reference type num += 114514; std::cout << num << std::endl; }
result:
Here it is recommended that if the value of the variable in the container is not modified, it is also declared as a reference type of const:
for (const int& num : nums) { std::cout << num << std::endl; }
The reason for this is briefly explained below. First, customize a class and overload the output stream operator (to save some trouble for output~)
#include<iostream> #include<vector> template<class Type> class complex { private: Type x; Type y; public: complex(Type x = Type(), Type y = Type()) :x(x), y(y) { std::cout << "constructor call" << std::endl; } complex(const complex& right) { x = right.x; y = right.y; std::cout << "copy constructor call" << std::endl; } template<class Type> friend std::ostream& operator<<(std::ostream &c, const complex<Type>& out); }; template<class Type> std::ostream& operator<<(std::ostream &c, const complex<Type>& out){ //Overloaded the output stream for easy output c << out.x << "," << out.y; return c; }
Then test with the following main function
int main() { std::vector<complex<int>> nums; nums.reserve(10); nums.emplace_back(1, 2); nums.emplace_back(3, 4); nums.emplace_back(5, 6); nums.emplace_back(7, 8); nums.emplace_back(9, 10); for (complex<int> num : nums) { std::cout << num << std::endl; } }
Take a look at the output:
The copy constructor is called five times, that is, one copy before each output. To avoid unnecessary copying, use reference types instead:
for (complex<int>& num : nums) { std::cout << num << std::endl; }
The simple principle eliminates the need for copying. Therefore, whether or not to modify the value of the variable in the container, it is recommended to use a reference. If no modification is required, use the const keyword to modify it.
internal mechanism
Question: If I customize a container (you can refer to the manually simulated array container mentioned above), if I want to use such a for loop, how do I need to implement it?
In the process of my own implementation, I came to a conclusion that it is the iterator. In the custom container type, as long as the iterator with the same interface as the standard library is defined, the range for loop can be used (need to define the iterator type, begin(), and end() )
To demonstrate this mechanism, let's quickly implement a castrated sequential container manually:
class array { private: int* list; unsigned int size; public: array(unsigned int n = 0) { list = new int[n]; size = n; for (int i = 0; i < n; ++i) { list[i] = 0; }//Simply initialize each value to 0; } int& operator[](const unsigned int &idx) { return *(list + idx); }//Overloading the subscript operator };
If at this point I use a range for loop on this container:
int main() { int n = 10; array nums(n); for (int &num : nums) { num = 5; std::cout << num << std::endl; } }
He reported an error like this:
So we define an iterator ourselves:
class array { private: int* list; unsigned int size; public: array(unsigned int n = 0) { list = new int[n]; size = n; _begin = list; _end = list + n; for (int i = 0; i < n; ++i) { list[i] = 0; }//Simply initialize each value to 0; } ~array() { delete[]list; }//Added destructor, just didn't add it because of negligence qwq int& operator[](const unsigned int &idx) { return *(list + idx); }//Overloading the subscript operator typedef int* iterator;//Defines an iterator of the array container type iterator begin() { return _begin; } iterator end() { return _end; } private: iterator _begin; iterator _end; };
Using the same test function again, there is no error
int main() { int n = 10; array nums(n); for (int &num : nums) { num = 5; std::cout << num << std::endl; } }
get the following output
As mentioned in the above error report, only the begin() interface is needed, then try deleting the iterator and leaving only begin() and end() to complete the range for loop:
The internal form is as follows
int* begin() { return list; } int* end() { return list + size; }
still got the correct result
Tip: If you only keep begin, an error will be reported
In the definition, iterators are generalized pointers, which means that pointers are actually special iterators. So if a custom container wants to use a range for loop, it must define an iterator and provide begin() and end() interfaces
For a range for loop can also be equivalent to the following code
for (int& num : nums) { std::cout << num << std::endl; } //The two spellings are equivalent for (std::vector<int>::iterator it = nums.begin(); it != nums.end(); ++it) { int& num = *it; std::cout << num << std::endl; }
Summarize
Range for loops can be very difficult to use in some places, especially when you need to operate on subscripts in the loop, or when you don't need to traverse all the elements of the container, but in certain cases it is very neat to write, especially when replacing iterators , very concise. This time, I dared to simply dig the underlying mechanism and wrote a blog. If there is something wrong, please criticize and correct me.