第9课 基于范围的for循环
Posted 浅墨浓香
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第9课 基于范围的for循环相关的知识,希望对你有一定的参考价值。
1. 基于范围的for循环(range-based for)
(1)语法:for(decl : coll){//statement}
①decl用于声明元素及类型,如int elem或auto elem(让编译器自动推导集合中元素的类型),但应该注意auto& elem和auto elem的区别,前者是元素的引用,后者是元素的副本。
②coll为元素的集合
(2)for新语法的等价语法
①利用coll容器类本身提供的迭代器:coll.begin()和coll.end()
for(auto _pos=coll.begin(),_end=coll.end(); _pos!=_end; ++pos){ decl=*_pos; //decl:如auto elem或auto& elem等 // other statement; }
②利用全局库函数(可重载):begin(coll)和end(coll)
for(auto _pos=begin(coll),_end=end(coll); _pos!=_end; ++pos){ decl=*_pos; // other statement; }
2. for循环的使用细节
(1)auto自动推导出的元素类型就是容器中的value_type,而不是迭代器的类型。
(2)auto会引用元素的拷贝,有时为了效率可以考虑使用auto&或const auto&。
(3)decl中要求元素的类型支持隐式类型转换。如:
const MyString& elem : vecString;(其中的vecString(类型为vector<string>)中元素的类型为string,而elem被声明为MyString,两者的类型是不同的,这会进行隐式转换,这就要求MyString类不能像explicit MyString(string);这样声明构造函数。
(4)不论基于范围的for循环迭代了多少次,冒号后面的表达式只会被执行一次!
(5)for循环的使用还受容器本身的一些约束,如std::set<int>中的内部元素是只读的。但使用auto&时,会被推导为const auto&。
(6)基于范围的for循环和普通for循环一样,在迭代时修改容器(增加或删除元素)可能会引起迭代器失效。
【编程实验】for新语法初探
#include <iostream> #include <vector> #include <map> using namespace std; //两种for循环中auto推导出来的元素类型的不同 void compare_for() { std::map<string, int> mm = {{"1",1},{"2",2},{"3",3}}; //以基于范围的for循环访式遍历(注意,auto推导出来的是元素的类型,而不是迭代器) for(auto& val : mm){ //val类型为std::pair类型,通过“.”访问元素的first和second成员 cout << val.first << "->" << val.second << ", "; } cout << endl; //以迭代器方式遍历元素(注意,iter为迭代器类型) for(auto iter=mm.begin(); iter!=mm.end();++iter){ //iter类型为迭代器,通过“->”来访问first和second成员 cout << iter->first << "->" << iter->second << ", "; } cout << endl; } //测试for循环对容器的访问频率 vector<int>& get_range(vector<int>& arr) { cout << "get_range ->: " << endl; return arr; } class MyString { public: explicit MyString(const string& s); //explicit阻止类型的隐式转换 }; int main() { //测试1:遍历initializer_list集合 for(int i: {2,3,5,7,9,13,17,19}){ cout << i << " "; } cout << endl; //测试2:以引用方式使用元素 vector<int> arr={1,2,3,4,5,6}; for(auto& n : arr){ n++; } for(const auto& n : arr){ cout << n << " "; } cout << endl; //测试3:两种for循环中auto推导出来的元素类型的不同 compare_for(); //测试4:冒号后面表达式被执行的次数 for(auto val : get_range(arr)){ //get_range()只被执行一次! cout << val << " "; } cout << endl; //测试4:隐式类型转换 // vector<string> vs; // for(const MyString& elem : vs){ //error, vector中的元素(string)无 // //法转成MyString,因为MyString阻止了隐式转换 // cout << elem << endl; // } return 0; } /*测试结果 e:\Study\C++11\9>g++ -std=c++11 test1.cpp e:\Study\C++11\9>a.exe 2 3 5 7 9 13 17 19 2 3 4 5 6 7 1->1, 2->2, 3->3, 1->1, 2->2, 3->3, get_range ->: 2 3 4 5 6 7 */
3. 让自定义类型支持基于范围的for循环
(1)基于范围的for循环只是普通for循环的语法糖。它需要查找到容器提供的begin和end迭代器。
(2)基于范围的for循环以下面的方式查找容器的begin和end。
①若容器是一个普通的array对象(如int arr[10]),那么begin将以array首地址,end将为首地址加容器的长度。
②若容器是一个类对象,那么range-based for将试图通过查找类的begin()和end()来找到迭代器。
③否则,range-based for将试图使用全局的begin和end函数来定位begin和end迭代器。
(3)自定义类支持range-base for需要满足的条件(参考前面的for的等价语法来理解)
①类中需要定义容器相关的迭代器(这里的迭代器是广义的,指针也属于该范畴)
②类中要有begin()和end()的成员方法,返回值为迭代器(或重载全局的begin()和end()也可以)
③迭代器必须支持!=、*解引用、前置++等操作。
【编程实验】自定义类:支持在指定区间中使用的range-based for遍历
//iterator.hpp:迭代器类
#ifndef _ITERATOR_H_ #define _ITERATOR_H_ #include <cstddef> namespace detail_range { //自定义可以对某个区间进行迭代的iterator,如[2, 14)区间,步长为2 template<typename T> class iterator { public: using value_type = T; using size_type = size_t; //size_t为unsigned int类型,非负整数 private: size_type cursor_; //从头开始需移动多少次才到到当前位置(必须是非负整数) const value_type step_; //step一旦被初始化,就不可改变(注意step可正可负) value_type value_; //当前游标的值 public: iterator(size_type cur_start, value_type begin_val, value_type step_val): cursor_(cur_start), step_(step_val),value_(begin_val) { //如范围为2, 4, 6, 8, 10, 12, 14,... 中 //begin_val指向2,step_val为2,cur_start:为从头开始需移动多少次才能到当前位置 value_ += (step_ * cursor_); } //迭代器必须支持*、!=、前置++等操作。 value_type operator*() const { return value_; } bool operator!=(const iterator& rhs) const { return (cursor_ != rhs.cursor_); } iterator& operator++(void) //前置++ { value_ += step_; ++cursor_; return (*this); } }; //end namespace } #endif //_ITERATOR_H_
//container.hpp:自定义的支持range-based for的容器类
#ifndef _CONTAINER_H_ #define _CONTAINER_H_ #include "iterator.hpp" namespace detail_range{ template<typename T> class container { public: //该类不支持外部修改其内部数据,所有暴露给外部的接口都加上了const using value_type = T; using reference = const value_type&; //加了const using const_reference = const value_type&; using iterator = const detail_range::iterator<value_type>; //加了const using const_iterator = const detail_range::iterator<value_type>; using size_type = typename iterator::size_type; //typename表示后面的内容是个类型 private: const value_type begin_; //begin迭代器 const value_type end_; //end迭代器 const value_type step_; const size_type max_count_; private: //计算元素的计数 size_type get_adjusted_count(void) const { if(step_ > 0 && begin_ >= end_){ throw std::logic_error("End value must be greater than begin value."); }else if(step_ < 0 && begin_<= end_){ throw std::logic_error("End value must be less than begin value."); } //获取元素个数(非负整数) size_type x = static_cast<size_type>( (end_ - begin_)/(step_)); if(begin_ + (step_ * x) != end_) ++x; //向上取整 return x; } public: container(value_type begin_val, value_type end_val, value_type step_val) :begin_(begin_val) ,end_(end_val) ,step_(step_val) ,max_count_(get_adjusted_count()) { } size_type size(void) const { return max_count_; } //为实现range-based for,必须提供begin()和end()方法,并返回迭代器类型! const_iterator begin(void) const { //用{}初始化返回值:是个迭代器! return {0, begin_, step_}; } const_iterator end(void) const { return {max_count_, begin_, step_}; } }; //range函数模板用于返回一个指定区间[begin, end)的容纳类对象 //返回[0, end)区间,步长为1的容器类对象 template<typename T> detail_range::container<T> range(T end) { return {{}, end, 1}; } //返回[begin, end)区间,步长为1的容器类对象 template<typename T> detail_range::container<T> range(T begin, T end) { return {begin, end, 1}; } //返回[begin, end)区间,步长为1的容器类对象 //注意由于T和U的类型可能不同,container中T类型为begin+step的类型 template<typename T, typename U> auto range(T begin, T end, U step)->detail_range::container<decltype(begin + step)> { using r_t = detail_range::container<decltype(begin + step)>; return r_t(begin, end, step); } } //end namespace #endif //_CONTAINER_H_
//main.cpp
#include <iostream> #include "container.hpp" using namespace std; using namespace detail_range; //遍历容器:采用range-based for方式 template <typename T> using const_Rng = const detail_range::container<T>; template<typename T> void traverse_range(const_Rng<T>& rng) { for(auto i : rng){ cout << " " << i; } cout << endl; } //测试 void test_range() { cout << "range(15): "; traverse_range(range(15)); //区间[0,15),step=1; cout << "range(2, 7): "; traverse_range(range(2, 7)); //区间[2,7),step=1; cout << "range(2, 6, 3): "; int x = 2, y = 6, z = 3; traverse_range(range(x, y, z)); //区间[2,6),step=3; cout << "range(-2, -6, -3): "; traverse_range(range(-2, -6, -3));//区间[-2,-6),step=-3; cout << "range(8, 7, -0.1): "; traverse_range(range(8, 7, -0.1));//区间[8,7),step=-0.1; cout << "range(10.5,15.5): "; traverse_range(range(10.5, 15.5));//区间[10.5,15.5),step=1; cout << "range(2, 8, 0.5): "; traverse_range(range(2, 8, 0.5));//区间[2,8),step=0.5; cout << "range(‘a‘, ‘z‘): "; traverse_range(range(‘a‘, ‘z‘));//区间[a,z),step=1; } int main() { test_range(); return 0; } /*测试结果: e:\Study\C++11\9>g++ -std=c++11 main.cpp e:\Study\C++11\9>a.exe range(15): 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 range(2, 7): 2 3 4 5 6 range(2, 6, 3): 2 5 range(-2, -6, -3): -2 -5 range(8, 7, -0.1): 8 7.9 7.8 7.7 7.6 7.5 7.4 7.3 7.2 7.1 range(10.5,15.5): 10.5 11.5 12.5 13.5 14.5 range(2, 8, 0.5): 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 range(‘a‘, ‘z‘): a b c d e f g h i j k l m n o p q r s t u v w x y */
以上是关于第9课 基于范围的for循环的主要内容,如果未能解决你的问题,请参考以下文章
c_cpp 这个简单的代码片段显示了如何使用有符号整数在C中完成插值。 for()循环确定要插入的范围