研究一下C++的范围for循环 Range-based for loop

研究一下C++的范围for循环 Range-based for loop(since C++11)的生命周期拓展

1 先来看看c++官方的解释

“Executes a for loop over a range.Used as a more readable equivalent to the traditional for loop operating over a range of values, such as all elements in a container.”
是个啥大家都了解
就是用迭代器遍历一个容器(就是用迭代器遍历一个容器)

2 看看它的语法定义

for ( range_declaration : range_expression ) loop_statement
codedescription
range_declarationa declaration of a named variable, whose type is the type of the element of the sequence represented by range_expression, or a reference to that type. Often uses the auto specifier for automatic type deduction
range_expressionany expression that represents a suitable sequence (either an array or an object for which begin and end member functions or free functions are defined, see below) or a braced-init-list.
loop_statementany statement, typically a compound statement, which is the body of the loop

主要分为三部分

  • (range_declaration):通常是个 auto 的对象来引用容器中的一个元素
  • (range_expression): 一个“合适”的序列,可迭代的容器
  • (loop_statement): 循环主体

3 整个例子

  std::vector<int> numbers = {1,2,3,4,5};
  for (auto n: numbers)
  {
     std::cout<<n;
  }

用起来挺简单的吧
接下来我们把 Range-based for loop的实现展开

4 Explanation

 {
    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

上面3的例子给它展开

{
    std::vector<int, std::allocator<int> > & __range1 = numbers;
    __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __begin1 = __range1.begin();
    __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __end1 = __range1.end();
    for(; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
      int n = __begin1.operator*();
      std::cout.operator<<(n);
    }
 }

好像也没哈 看起来就是用个迭代器去访问里边的元素

可是外边为啥包个大括号?
__range 为啥是个右值引用呢?

精髓来了。它就是这段话

5 Temporary range expression

If range_expression returns a temporary, its lifetime is extended until the end of the loop, as indicated by binding to the forwarding reference __range, but beware that the lifetime of any temporary within range_expression is not extended.

为什么要这么搞呢?

  • 试想一下,如果range_expression是一个函数返回的临时对象,那么 __range 就可以引用这个对象,并将其生命周期延长到外层包的的括号内。这样使用右值引用不仅减少了变量的拷贝,同时最外层的大括号也保证了生命周期。

看起来是个不错的语法糖

可接下要看个坑
看下面代码

std::vector<std::vector<std::string>> func() {
  return {{“lebron”, "MVP"}, {“lakers, “champion”}};
}
auto strs = func().at(0);

for (auto str : strs) {
    cout << str; // lebronMVP 
}
for (auto str : func().at(0)) {
    cout << str;//null 
}
看起来这两个for循环 好像没啥差别,可是第二个for却没有遍历到数据,这是为什么???
让我们把模版特例化展开
std::vector<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > >, std::allocator<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > > > strs = func();
  {
    std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > & __range1 = strs.at(0);!!!
    __gnu_cxx::__normal_iterator<std::basic_string<char> *, std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > > __begin1 = __range1.begin();
    __gnu_cxx::__normal_iterator<std::basic_string<char> *, std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > > __end1 = __range1.end();
    for(; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
      std::basic_string<char> str = std::basic_string<char>(__begin1.operator*());
      std::operator<<(std::cout, str);
    }
    
  }
  {
    std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > & __range1 = func().at(0);!!!!!
    __gnu_cxx::__normal_iterator<std::basic_string<char> *, std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > > __begin1 = __range1.begin();
    __gnu_cxx::__normal_iterator<std::basic_string<char> *, std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > > __end1 = __range1.end();
    for(; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
      std::basic_string<char> str = std::basic_string<char>(__begin1.operator*());
      std::operator<<(std::cout, str);
    }
    
  }

关键在于__range1 所引用变量的生命周期,
第一个for 中& __range1 = strs.at(0); 引用的是一个局部变量 str 的第一个数据块,它的生命周期大于for的循环体所以可以正常遍历。
可是第二个for中& __range1 = func().at(0); 引用的是fun函数返回的临时变量的第一个数据块,这个数据块虽然试图被__range1的引用延长生命周期到for的大括号内,可是函数返回的临时变量已经被销毁了,其数据块自然也被release了,所以也就没法遍历了。

而在C++ 20中准备了,

6 Init-statement (C++20)

如下:
对于c++17

for (auto& x : foo().items()) { /* .. */ } // undefined behavior if foo() returns by value

在c++ 20 中增加了 Init-statement

for ( init-statement(optional)range_declaration : range_expression )loop_statement
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK	(since C++20)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值