• 【C++语法糖】范围for


    【C++语法糖】:范围for

    C++11标准后引入了范围for,这个范围for是一种语法糖,来简化代码书写。

    下面是简单遍历数组的一段简单代码

    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
    

    但是如果将数组换成不连续存储的链表list,就不能使用上面的方式,得用迭代器

    list lt = { 1,2,3,4,5,6 };
    list::iterator lit = lt.begin();
    while (lit != lt.end())
    {
    	cout << *lit << "->";
    	++lit;
    }
    cout << endl;
    

    我们可以看到这段代码还是相对比较麻烦的,C++11以后引入了范围for的语法糖就大大简化了上面的代码。

    list lt = { 1,2,3,4,5 };
    for (auto& k : lt)
    {
    	cout << k << "->";
    }
    cout << endl;
    

    范围for

    • 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号 " : " 分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
    • 范围for可以作用到数组和容器对象上进行遍历
    • 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到

    1. 范围for的模板

    for (auto 迭代的变量 : 迭代的范围)
    {
        循环体
    }
    
    for (auto& 迭代的变量 : 迭代的范围)
    {
        循环体
    }
    

    2. &的使用

    下面这两段虽然输出结果一样,但是有着本质上的区别。

    list lt = { 1,2,3,4,5 };
    for (auto k : lt)
    {
    	k += 2;
        cout << k << "->";
    }
    cout << endl;
    

    完成上面的操作后,lt中的值没有发生变化

    list lt = { 1,2,3,4,5 };
    for (auto& k : lt)
    {
        k += 2;
    	cout << k << "->";
    }
    cout << endl;
    

    完成上面的操作后,lt中每个值都+=2

    从语法上理解:

    如果直接采用第一种方法,就是将lt中每个值依次拷贝给k,然后完成循环体中的操作。

    如果采用第二种方法,k依次是lt中每个值的别名,然后进行循环体中的操作。

    但是实际上,两种方案在内存上没有区别,因为两者底层都是采用迭代器,只是两种语法对迭代器变量的操作不同而已。

    3. 范围for的底层

    范围for有两种情况:

    1. 遍历容器的时候

      	for (auto& k : lt)
      00007FF6A6645810  lea         rax,[lt]  
      00007FF6A6645814  mov         qword ptr [rbp+38h],rax  
      00007FF6A6645818  mov         edx,8  
      00007FF6A664581D  lea         rcx,[rbp+58h]  
      00007FF6A6645821  call        std::_List_unchecked_iterator > >::__autoclassinit2 (07FF6A66413E8h)  
      00007FF6A6645826  lea         rdx,[rbp+58h]  
      00007FF6A664582A  mov         rcx,qword ptr [rbp+38h]  
      00007FF6A664582E  call        std::list >::_Unchecked_begin (07FF6A66414F1h)  
      00007FF6A6645833  mov         edx,8  
      00007FF6A6645838  lea         rcx,[rbp+78h]  
      00007FF6A664583C  call        std::_List_unchecked_iterator > >::__autoclassinit2 (07FF6A66413E8h)  
      00007FF6A6645841  lea         rdx,[rbp+78h]  
      00007FF6A6645845  mov         rcx,qword ptr [rbp+38h]  
      00007FF6A6645849  call        std::list >::_Unchecked_end (07FF6A66413A2h)  
      00007FF6A664584E  jmp         __$EncStackInitStart+0F9h (07FF6A6645859h)  
      00007FF6A6645850  lea         rcx,[rbp+58h]  
      00007FF6A6645854  call        std::_List_unchecked_iterator > >::operator++ (07FF6A66411FEh)  
      00007FF6A6645859  lea         rdx,[rbp+78h]  
      00007FF6A664585D  lea         rcx,[rbp+58h]  
      00007FF6A6645861  call        std::_List_unchecked_const_iterator >,std::_Iterator_base0>::operator!= (07FF6A66412F8h)  
      00007FF6A6645866  movzx       eax,al  
      00007FF6A6645869  test        eax,eax  
      00007FF6A664586B  je          __$EncStackInitStart+144h (07FF6A66458A4h)  
      00007FF6A664586D  lea         rcx,[rbp+58h]  
      00007FF6A6645871  call        std::_List_unchecked_iterator > >::operator* (07FF6A664157Dh)  
      00007FF6A6645876  mov         qword ptr [rbp+98h],rax  
      

      我们可以看到,在遍历容器时,编译器会将范围for转换成迭代器去遍历容器。

    2. 遍历数组的时候

      // 范围for
      	for (auto k : arr)
      00007FF6D28C1973  lea         rax,[arr]  
      00007FF6D28C1977  mov         qword ptr [rbp+48h],rax  
      00007FF6D28C197B  mov         rax,qword ptr [rbp+48h]  
      00007FF6D28C197F  mov         qword ptr [rbp+68h],rax  
      00007FF6D28C1983  mov         rax,qword ptr [rbp+48h]  
      00007FF6D28C1987  add         rax,28h  
      00007FF6D28C198B  mov         qword ptr [rbp+88h],rax  
      00007FF6D28C1992  jmp         __$EncStackInitStart+0A1h (07FF6D28C19A0h)  
      00007FF6D28C1994  mov         rax,qword ptr [rbp+68h]  
      00007FF6D28C1998  add         rax,4  
      00007FF6D28C199C  mov         qword ptr [rbp+68h],rax  
      00007FF6D28C19A0  mov         rax,qword ptr [rbp+88h]  
      00007FF6D28C19A7  cmp         qword ptr [rbp+68h],rax  
      00007FF6D28C19AB  je          __$EncStackInitStart+0BCh (07FF6D28C19BBh)  
      00007FF6D28C19AD  mov         rax,qword ptr [rbp+68h]  
      00007FF6D28C19B1  mov         eax,dword ptr [rax]  
      00007FF6D28C19B3  mov         dword ptr [rbp+0A4h],eax  
      	{
      
      	}
      00007FF6D28C19B9  jmp         __$EncStackInitStart+95h (07FF6D28C1994h)
      
      // 普通for
      	for (int i = 0; i < sizeof(arr) / sizeof(int); ++i)
      00007FF6D28C19BB  mov         dword ptr [rbp+0C4h],0  
      00007FF6D28C19C5  jmp         __$EncStackInitStart+0D6h (07FF6D28C19D5h)  
      00007FF6D28C19C7  mov         eax,dword ptr [rbp+0C4h]  
      00007FF6D28C19CD  inc         eax  
      00007FF6D28C19CF  mov         dword ptr [rbp+0C4h],eax  
      00007FF6D28C19D5  movsxd      rax,dword ptr [rbp+0C4h]  
      00007FF6D28C19DC  cmp         rax,0Ah  
      00007FF6D28C19E0  jae         __$EncStackInitStart+0E5h (07FF6D28C19E4h)  
      	{
      	}
      00007FF6D28C19E2  jmp         __$EncStackInitStart+0C8h (07FF6D28C19C7h)
      

      由此我们可以看到,在遍历数组时,编译器会将范围for转换成普通的for循环语句来遍历数组。

  • 相关阅读:
    C# 连接SQL Sever 数据库与数据查询实例 数据仓库
    大一作业HTML网页作业:简单的旅游 1页 (旅游主题)
    哈希——开散列
    ADSO的Remodeling
    如何构建一个 NodeJS 影院微服务并使用 Docker 部署
    神经网络 06(优化方法)
    26.CF1000F One Occurrence
    【限时免费】20天拿下华为OD笔试之【哈希集合】2023B-明明的随机数【欧弟算法】全网注释最详细分类最全的华为OD真题题解
    springboot+APP基于Android的洗衣店预约系统毕业设计源码260839
    第六章:函数(上)
  • 原文地址:https://blog.csdn.net/Alenenen/article/details/141003210