博客 / 詳情

返回

基於範圍的for循環

c++11基於範圍的for循環,語法:

for (Type declaration : expression)
{
    // 循環體
}

在上面的語法格式中Type declaration表示遍歷聲明,在遍歷過程中,當前被遍歷導的元素會被存儲到聲明的變量declaration中。expression是要遍歷的對象,它可以是表達式、容器、數組、初始化列表等。

如下代碼:

#include <iostream>
#include <vector>
using namespace std;

int main(void)
{
    vector<int> t{ 1,2,3,4,5,6 };
    for (auto value : t){    //第一次遍歷
        cout << value++ << " ";
    }
    cout << endl;

    for(int value : t){           //第二次遍歷
        cout << value << " ";
    }
    
    cout << endl;
    
    for(auto& value : t){        //第三次遍歷
        cout << value++ << " ";
    }
    
    cout << endl;
    
    for(auto& value : t){          //第四次遍歷
        cout << value << " ";
    }
    
    cout << endl;
    
    for(const auto& value : t){      //第五次遍歷
        cout << value << " ";
    }
    
    return 0;
}

運行結果:

1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
2 3 4 5 6 7
2 3 4 5 6 7

在上面的例子中,第一次遍歷是將容器中的元素拷貝到聲明的遍歷變量value中, 因此無法對value的++操作是不影響原數據的,所以第二次遍歷的結果不會有改變。第二次遍歷中聲明遍歷變量用的int,因為這裏知道遍歷的t容器中全是int類型,auto推導出的就是int類型。

第三次遍歷時,遍歷變量value聲明成引用類型,不僅沒有拷貝的過程使得效率更高,而且對value的++操作會直接作用到原數據上,因此第四次遍歷的結果會全部+1。

在第五次遍歷時,遍歷變量聲明稱const auto&,value被限制成只讀權限,如果對value進行++操作會報錯。

使用注意

1. 關係型容器

在使用基於範圍的for循環遍歷map容器時:

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

int main(void)
{
    map<int, string> m{
        {1, "lucy"},{2, "lily"},{3, "tom"}
    };

    // 基於範圍的for循環方式
    for (auto& it : m)
    {
        cout << "id: " << it.first << ", name: " << it.second << endl;
    }

    // 普通的for循環方式
    for (auto it = m.begin(); it != m.end(); ++it)
    {
        cout << "id: " << it->first << ", name: " << it->second << endl;
    }

    return 0;
}

上述代碼使用了基於範圍的for循環方式和普通的for循環方式兩種方式對map進行遍歷,注意到:

  • 使用普通for循環遍歷關係型容器時,auto自動推導出的是一個迭代器類型,需要使用迭代器的類型方式取出元素中的鍵值對,迭代器返回的是地址:

    it->first;

    it->second;

  • 使用基於範圍的for循環方式遍歷關係型容器時,auto自動推導出的類型是容器中的value_type,相當於一個std::pair對象,提取鍵值對的方式:

    it.first;

    it.second;

2. 元素只讀

在對基於範圍的for循環語法的介紹中可以得知,在for循環內部聲明一個變量的引用就可以修改遍歷的表達式中的元素的值,但是這並不是用於所有的情況,對應set容器來説,內部元素都是隻讀的,這是由容器的特性決定的,因此在for循環中auto&會被是為const auto&。

#include <iostream>
#include <set>
using namespace std;

int main(void)
{
    set<int> st{ 1,2,3,4,5,6 };
    for (auto &item : st) 
    {
        cout << item++ << endl;		// error, 不能給常量賦值
    }
    return 0;
}

除此之外,在遍歷關係型容器map時也會出現同樣的問題,基於範圍的for循環中,雖然可以得到一個std::pair引用,但是我們是不能修改裏面的first值的,也就是key值。

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

int main(void)
{
    map<int, string> m{
        {1, "lucy"},{2, "lily"},{3, "tom"}
    };

    for (auto& item : m)
    {
        // item.first 是一個常量
        cout << "id: " << item.first++ << ", name: " << item.second << endl;  // error
    }

    return 0;
}
訪問次數

基於範圍for循環遍歷的對象可以是一個表達式或者容器/數組等。在我們對一個容器進行遍歷過程中對這個容器的訪問次數時一次還是多次呢?

#include <iostream>
#include <vector>
using namespace std;

vector<int> v{ 1,2,3,4,5,6 };
vector<int>& getRange()
{
    cout << "get vector range..." << endl;
    return v;
}

int main(void)
{
    for (auto val : getRange())
    {
        cout << val << " ";
    }
    cout << endl;

    return 0;
}

上面代碼通過getRange()函數對容器v進行訪問,每訪問一次就會輸出一次get vector range...

輸出結果:

get vector range...
1 2 3 4 5 6

可以從上面的結果看出只訪問了一次,所以不管遍歷的容器裏面有多少個元素,都只在第一次迭代之前被訪問一次,得到這個容器對象之後不會再去重新獲取這個對象了。

對應基於範圍的for循環來説,冒號後邊的表達式只會被執行一次。在得到遍歷對象之後會先確定好迭代的範圍,基於這個範圍直接進行遍歷。如果是普通的for循環,在每次迭代的時候都需要判斷是否已經到了結束邊界。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.