• 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循环中修改容器到底会造成什么样的影响非常困难。

  • 相关阅读:
    C# 模拟串口发送接收
    maven update解决TypeMismatchException
    [转载]JUnit3 与 JUnit4 的区别
    JUnit版本导致eclipse的build path加入的Maven Dependencies没起作用
    [转载][oracle]使用exp导出数据的日志中报warning如EXP-00091 Exporting questionable statistics
    [框架][MyBatis]MyBatis集锦
    [转载][工具]Secure CRT 自动记录日志和时间戳功能配置的方法
    jUnit Test遇到org.apache.ibatis.binding.BindingException
    [转载[工具]]PLSQL使用技巧
    [转载][工具]Eclipse Console 加大显示的行数,禁止弹出
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/7658331.html
Copyright © 2020-2023  润新知