首页 > 编程语言 >C++ - 基于范围的 for 循环

C++ - 基于范围的 for 循环

时间:2023-10-11 17:38:20浏览次数:35  
标签:std 容器 基于 遍历 auto C++ 循环 include

在 C++98/03 中,不同的容器和数组遍历的方式不尽相同,写法不统一,也不够简洁,而 C++11 基于范围的 for 循环可以以简洁、统一的方式来遍历容器和数组,用起来也更方便了。

1. for 循环新语法

在介绍新语法之前,先来看一个使用迭代器遍历容器的例子:

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

int main()
{
    vector<int> t{ 1,2,3,4,5,6 };
    for (auto it = t.begin(); it != t.end(); ++it)
    {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}


我们在遍历的过程中需要给出容器的两端:开头(begin)和结尾(end),因为这种遍历方式不是基于范围来设计的。在基于范围的for循环中,不需要再传递容器的两端,循环会自动以容器为范围展开,并且循环中也屏蔽掉了迭代器的遍历细节,直接抽取容器中的元素进行运算,使用这种方式进行循环遍历会让编码和维护变得更加简便。

C++98/03 中普通的 for 循环,语法格式:

for(表达式 1; 表达式 2; 表达式 3)
{
    // 循环体
}



C++11 基于范围的 for 循环,语法格式:

for (declaration : expression)
{
    // 循环体
}



在上面的语法格式中 declaration 表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression 是要遍历的对象,它可以是表达式、容器、数组、初始化列表等。

使用基于范围的 for 循环遍历容器,示例代码如下:

#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;

    return 0;
}


在上面的例子中,是将容器中遍历的当前元素拷贝到了声明的变量 value 中,因此无法对容器中的元素进行写操作,如果需要在遍历过程中修改元素的值,需要使用引用。

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

int main(void)
{
    vector<int> t{ 1,2,3,4,5,6 };
    cout << "遍历修改之前的容器: ";
    for (auto &value : t)
    {
        cout << value++ << " ";
    }
    cout << endl << "遍历修改之后的容器: ";

    for (auto &value : t)
    {
        cout << value << " ";
    }
    cout << endl;

    return 0;
}


代码输出的结果:

遍历修改之前的容器: 1 2 3 4 5 6
遍历修改之后的容器: 2 3 4 5 6 7

 

对容器的遍历过程中,如果只是读数据,不允许修改元素的值,可以使用 const 定义保存元素数据的变量,在定义的时候建议使用 const auto &,这样相对于 const auto 效率要更高一些。

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

int main(void)
{
    vector<int> t{ 1,2,3,4,5,6 };
    for (const auto& value : t)
    {
        cout << value << " ";
    }

    return 0;
}


2. 使用细节

2.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;
}


在上面的例子中使用两种方式对 map 进行了遍历,通过对比有两点需要注意的事项:

  • 使用普通的 for 循环方式(基于迭代器)遍历关联性容器, auto 自动推导出的是一个迭代器类型,需要使用迭代器的方式取出元素中的键值对(和指针的操作方法相同):
it->first

it->second
  • 使用基于范围的 for 循环遍历关联性容器,auto 自动推导出的类型是容器中的 value_type,相当于一个对组(std::pair)对象,提取键值对的方式如下:
it.first

it.second

2.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;
}


除此之外,在遍历关联型容器时也会出现同样的问题,基于范围的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;
}


2.3 访问次数

基于范围的 for 循环遍历的对象可以是一个表达式或者容器 / 数组等。假设我们对一个容器进行遍历,在遍历过程中 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;
}


输出的结果如下:

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


从上面的结果中可以看到,不论基于范围的 for 循环迭代了多少次,函数 getRange () 只在第一次迭代之前被调用,得到这个容器对象之后就不会再去重新获取这个对象了。

结论:

对应基于范围的 for 循环来说,冒号后边的表达式只会被执行一次。在得到遍历对象之后会先确定好迭代的范围,基于这个范围直接进行遍历。如果是普通的 for 循环,在每次迭代的时候都需要判断是否已经到了结束边界。

 


作者: 苏丙榅

链接: https://subingwen.cn/cpp/for

来源: 爱编程的大丙

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签:std,容器,基于,遍历,auto,C++,循环,include
From: https://www.cnblogs.com/zhuchunlin/p/17757720.html

相关文章

  • C++ - move()函数
    C++11标准中借助右值引用可以为指定类添加移动构造函数,这样当使用该类的右值对象(可以理解为临时对象)初始化同类对象时,编译器会优先选择移动构造函数。注意,移动构造函数的调用时机是:用同类的右值对象初始化新对象。那么,用当前类的左值对象(有名称,能获取其存储地址的实例对象)初始化......
  • C++ - 使用using定义别名
    大家都知道,在 C++ 中可以通过typedef重定义一个类型:typedefunsignedintuint_t;被重定义的类型并不是一个新的类型,仅仅只是原有的类型取了一个新的名字。因此,下面这样将不是合法的函数重载:voidfunc(unsignedint);voidfunc(uint_t); //error:redefinition使用ty......
  • C++ - 右值引用
    《C++11是什么》一节中提到,在C++98/03标准的基础上,C++11标准对C++语言增添了约140个新特性。本节要讲的右值引用就是众多新特性中的一个,同时也是最重要的特性之一。很多初学者都感觉右值引用晦涩难懂,其实不然。右值引用只不过是一种新的C++语法,真正理解起来有难度的是基......
  • C++ - 单例模式实现
    1.什么是单例模式单例模式是指在整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。为什么需要单例模式两个原因:节省资源。一个类只有一个实例,不存在多份实例,节省资源。方便控制。在一些操作公共资源的场景时,避免了多个对象引起的复杂操作。但是在实现单例......
  • C++回调C#方法
    在VC中封装的网络通信模块,在异步接收到数据时需要将内容传递给C#中的消息处理函数,于是便出现了如标题所说的情况。   C++的回调函数中有一个参数,是处理接收到的字节流的回调函数指针,定义基本如下:   typedefvoid(*fpDataReceived)(char*data,intlen);......
  • C++ - 多线程之线程管理函数
    1.获取线程id函数get_id()的使用该函数在命名空间std::this_thread下。作用是获取当前线程的id。#include<iostream>#include<thread>usingnamespacestd;//No.1get_id()获取线程idvoidthreadFunc(){ cout<<"get_id()子线程id:"<<this_thread::get_id(......
  • C++ - 多线程之带返回值的线程处理函数
    1.使用async函数创建线程1.1使用步骤使用async函数启动一个异步任务(创建线程,并且执行线程处理函数),返回future对象通过future对象中get()方法获取线程处理函数的返回值1.2基本数据类型作为返回值#include<iostream>#include<thread>#include<future>using......
  • C++ - 多线程之线程同步
    1.多线程的并发问题线程间为什么需要同步?直接来看一个例子:inta=0;voidfoo(){ for(inti=0;i<10000000;++i) { a+=1; }}intmain(){ clock_tstart,end; start=clock(); threadt1(foo); threadt2(foo); t1.join(); t2.join(); end=clock();......
  • C++ - VS2019配置pthread线程库
    1.说明在VS里用MS编译器不能直接调用pthread库,需要先自行下载该库:http://sourceware.org/pub/pthreads-win32/pthreads-w32-2-9-1-release.zip解压后用的到的只有Pre-built.2文件夹下的文件。 2.配置如下图分别配置三大项:包含目录-->...pthreads-w32-2-9-1-release\Pre-......
  • C++ - TCP通信
    1.前言socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等。如下图:1.1头文件与库:#include<WinSock2.h>​#pragmacomment(lib,"ws2_32.lib")1.2准备工作:创建工程后,首先右键工程,选择属性然后选择......