1. 程式人生 > >第9課 基於範圍的for循環

第9課 基於範圍的for循環

解引用 blog lis “.” 技術 opened 叠代器 static list

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_
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:\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循環