C11簡潔之道:循環的改善


1、  for循環的新用法

  在C++98/03中,通過for循環對一個容器進行遍歷,一般有兩種方法,常規的for循環,或者使用<algorithm>中的for_each方法。

  for循環遍歷:

void func(void)
{
    std::vector<int> arr;
    for(auto it = arr.begin(); it != arr.end(); ++it)
    {
        std::cout << *it << std::endl;
    }
}

  for_each方法:

void vFuncCall(int n)
{
    std::cout << n << std::endl;
}

void func2(void)
{
    std::vector<int> arr;
    std::for_each(arr.begin(), arr.end(), vFuncCall);
}

  for_each相比一般的for循環,只需關注容器元素的類型,但是都是基於范圍的循環,必須顯示的給出容器的開始(begin)和結束(end)。C++11中改善了這種遍歷方式,不再需要給出容器的兩端,循環會自動根據容器的范圍自動的展開,在循環中屏蔽了迭代器的遍歷細節,直接抽取容器中的元素進行運算。我們來看C++11中是怎么遍歷容器的:

void func(void)
{
    std::vector<int> arr;
    for(auto n : arr)
    {
        std::cout << n << std::endl;
    }
}

  是不是很簡潔,n表示arr中的一個元素, auto會被編譯器自動推導出類型,此例中被推導為vector中的int類型。當然,也可以直接寫出類型:

std::vector<int> arr;
for(int n : arr)

  同時,這種循環中,冒號前面的變量支持隱式轉換的,因此在使用時需要注意:

std::vector<int> arr;
for(char c : arr)   //int會被隱式轉換為char

  在這種循環中,我們都是只讀方式來遍歷容器的,如果需要改變容器中的值,我們需要加上引用,如果是只希望遍歷而不是修改,我們可以使用const auto & 來定義n的類型,這樣對於復制負擔比較大的容器元素(std::vector<std::string>數組等)也可以無耗的進行遍歷。

std::vector<int> arr;
for(auto & n : arr)
{
    std::cout << n++ << std::endl;  //打印,並把元素的值+1
}

2、  使用細節

2.1 推導類型

  我們來看使用范圍的for循環和普通的for循環有什么區別:

std::map<std::string, int> mmsi_test = {{"1", 1}, {"2", 2}, {"3", 3}};

//一般情況的for循環
for(auto it = mmsi_test.begin(); it != mmsi_test.end(); ++it)
{
    std::cout << it->first << "->" << it->second << std::endl;
}

//基於范圍的for循環
for(auto val : mmsi_test)
{
    std::cout << val.first << "->" << val.second << std::endl;
}

  可以看出:

  •  for循環中的val類型是std::pair,對於map這種關聯性容器來說,需要使用val.first或者val.second來提取鍵值;
  • auto自動推導出來容器元素的類型是value_type,而不是迭代器。

2.2 容器約束

  如果我們要改變某些容器元素的值,通過auto &可以解決大多數問題,但是某些特殊容器並不能達到我們預期的結果。比如我們希望在循環中對set的值進行修改,但是set的內部元素的值是可讀的----由set容器的特性決定的,因此for循環中的auto &會被推導為const xx &。同樣基於范圍的map遍歷一樣,for循環得到的std::pair引用,是不能修改first的。

void func(void)
{
    std::set<int> ss = {1, 2, 3};
    for(auto &val : ss)
    {
        //error increment of read-only refrence 'val'
        std::cout << val++ << std::endl;
    }
}

2.3訪問頻率

  我們先來看一段代碼,測試一下C++11中的循環對於容器的訪問頻率。

#include <iostream>
#include <vector>
 
std::vector<int> g_arr = {1,2,3,4,5};

std::vector<int>& func(void)
{
    std::cout << "get range->" << std::endl;
    return g_arr;
}

int main(void)
{
    for(auto val : func())
    {
           std::cout << val << std::endl;
    }

    return 0;
}

  程序的執行結果:

 

  我們可以從結果中看出,不論基於范圍的for循環迭代了多少次,func()只在第一次迭代之前被調用,在循環之前就確定好迭代的范圍,而不是在每次迭代之前都去調用一次end()。所以可以得出結論:基於范圍的for循環,冒號后面的表達式只會被執行一次。

  那么我們來看如果在基於范圍的for循環中修改容器會出現什么情況:

#include <iostream>
#include <vector>

int main(void)
{
    std::vector<int> arr = {1,2,3,4,5};
    for(auto val : arr)
    {
        std::cout << val << std::endl;
        arr.push_back(100); //擴大容器
    }
         
    return 0;
}

  在centos6.7 64位系統運行結果:

 

  從結果看出,這並不是我們需要的結果,如果把vector換成list,結果又不一樣。

  因為介於范圍的for循環其實是普通for循環的語法糖,同普通的循環一樣,在迭代時修改容器可能引起迭代器失效,導致一些意料之外的結果。由於我們這里是沒法看到迭代器的,所以在基於范圍的for循環中修改容器到底會造成什么樣的影響非常困難。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM