zoukankan      html  css  js  c++  java
  • 第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:StudyC++119>g++ -std=c++11 test1.cpp
    e:StudyC++119>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_
    View Code

    //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:StudyC++119>g++ -std=c++11 main.cpp
    e:StudyC++119>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
    */
  • 相关阅读:
    MongoDB ‘conn’Mongo 对象远程代码执行漏洞
    Linux Kernel 本地拒绝服务漏洞
    Linux Kernel ‘skbuff.c’本地拒绝服务漏洞
    WordPress Citizen Space插件跨站请求伪造漏洞
    OpenSSH远程拒绝服务漏洞
    Bug之王花落谁家:四大最危险编程语言,PHP竟然不是bug最多的语言!
    《出Bug表》假如诸葛亮是程序员!写Bug测Bug,不宜异同!
    关于程序员的段子,有没有get到你的点?单身的程序员才是完整的程序员!
    关于程序员的段子,有没有get到你的点?单身的程序员才是完整的程序员!
    socket 通信 入门3 android 客户端 C# 服务端
  • 原文地址:https://www.cnblogs.com/5iedu/p/7629930.html
Copyright © 2011-2022 走看看