讲师中心 微信公众号

工具推荐

视频效率加速
AniShort
AniShort

AI短剧协同制作平台

updream

一键生成高质量推广成片

Seko
Seko

AI短剧一键生成成片

Laper
Laper

剧本策划到成片协作

c++如何使用范围for循环_c++基于范围的for循环使用技巧

雨瑶同学_9671

雨瑶同学_9671

发布时间:2025-09-25 13:30:03

|

1032人浏览过

|

来源于php中文网

原创

范围for循环通过简洁语法遍历容器,支持按值、常量引用或引用访问元素,提升代码可读性与安全性,底层由编译器转换为迭代器循环,效率接近传统方式;结合C++17结构化绑定可优雅处理键值对,但无法直接获取索引、安全修改容器大小或反向遍历,需借助计数器、std::views::enumerate、传统迭代器等替代方案。

c++如何使用范围for循环_c++基于范围的for循环使用技巧

C++中,范围for循环提供了一种极其简洁且直观的方式来遍历容器或数组的元素,无需手动管理迭代器或索引,大大提升了代码的可读性和编写效率。它的核心思想就是“对范围内的每一个元素执行某个操作”,让遍历操作变得前所未有的轻松。

解决方案

使用C++基于范围的for循环,语法结构非常直接:for (declaration : expression)。

这里的expression可以是一个数组、std::vector、std::list、std::map等任何具有begin()和end()成员函数或自由函数的类型(即满足Range概念的类型)。declaration则是用于接收expression中每个元素的类型声明。

通常,我们会根据需求选择以下几种declaration方式:

立即学习“C++免费学习笔记(深入)”;

  1. 按值拷贝 (by value): for (auto element : container) 这种方式会为container中的每个元素创建一个副本。如果你只需要读取元素且容器元素不大,或者希望在循环体内修改副本而不影响原容器,可以使用这种方式。但对于大对象,频繁拷贝会带来性能开销。

    #include <iostream>
    #include <vector>
    #include <string>
    
    int main() {
        std::vector<int> numbers = {1, 2, 3, 4, 5};
        std::cout << "按值拷贝遍历 (不改变原容器): ";
        for (int num : numbers) { // num是numbers中元素的副本
            num += 10; // 改变的是副本,不影响原numbers
            std::cout << num << " ";
        }
        std::cout << std::endl;
        std::cout << "原容器内容: ";
        for (int num : numbers) {
            std::cout << num << " "; // 输出仍是 1 2 3 4 5
        }
        std::cout << std::endl;
        return 0;
    }
  2. 按常量引用 (by const reference): for (const auto& element : container) 这是最常用且推荐的方式,特别是在你只需要读取元素而不需要修改它们时。它避免了元素的拷贝,直接引用原容器中的元素,效率高,同时通过const关键字保证了元素的不可修改性,增强了代码的安全性。

    #include <iostream>
    #include <vector>
    
    int main() {
        std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
        std::cout << "按常量引用遍历: ";
        for (const auto& name : names) { // name是names中元素的常量引用
            // name += "!"; // 编译错误:不能修改常量引用
            std::cout << name << " ";
        }
        std::cout << std::endl;
        return 0;
    }
  3. 按引用 (by reference): for (auto& element : container) 当你需要在循环体内修改容器中的元素时,使用引用方式。它同样避免了拷贝,直接操作原容器的元素。

    #include <iostream>
    #include <vector>
    
    int main() {
        std::vector<int> scores = {85, 90, 78, 92};
        std::cout << "修改前分数: ";
        for (int score : scores) {
            std::cout << score << " ";
        }
        std::cout << std::endl;
    
        std::cout << "按引用修改分数 (全部加5): ";
        for (auto& score : scores) { // score是scores中元素的引用
            score += 5; // 直接修改原容器中的元素
            std::cout << score << " ";
        }
        std::cout << std::endl;
        std::cout << "修改后分数: ";
        for (int score : scores) {
            std::cout << score << " "; // 输出 90 95 83 97
        }
        std::cout << std::endl;
        return 0;
    }

我个人觉得,这玩意儿一出来,写代码的幸福感真是直线飙升,特别是告别了那些std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it</int>的冗长写法,代码瞬间清爽了不少。

范围for循环背后的机制与效率考量

范围for循环之所以如此便捷,是因为它在编译时会被C++编译器“翻译”成我们熟悉的、基于迭代器的传统for循环。具体来说,对于一个形如for (declaration : expression)的范围for循环,编译器会大致将其展开为:

{
    auto&& __range = expression;
    auto __begin = std::begin(__range);
    auto __end = std::end(__range);
    for (; __begin != __end; ++__begin) {
        declaration = *__begin;
        // 循环体
    }
}

这里std::begin和std::end是全局函数,它们会优先调用expression的成员begin()和end(),如果没有,则会尝试寻找全局的begin(expression)和end(expression)函数。这意味着,任何提供了有效begin()和end()函数的类型,都可以作为范围for循环的expression。

说实话,刚开始用的时候,我压根没想过它底层是怎么实现的,只觉得好用。后来深入了解,才发现它其实是编译器的一个语法糖,把那些繁琐的迭代器操作给封装起来了。这让我对C++的现代化改进又多了一分敬佩,它在保持底层控制力的同时,也努力让高层代码更具表现力。

效率考量:

  • 与传统循环对比: 对于std::vector或C风格数组这类内存连续的容器,范围for循环的效率通常与基于索引的循环(for (size_t i = 0; i )或基于迭代器的循环(<code>for (auto it = vec.begin(); it != vec.end(); ++it))相当。编译器优化做得好的话,甚至可能因为其意图更明确而得到更好的优化。
  • 选择declaration类型:
    • const auto& (常量引用): 这是效率最高且最安全的遍历方式,因为它避免了不必要的拷贝,并且防止了意外修改。在绝大多数只读遍历场景中,都应该优先使用它。
    • auto& (引用): 当你需要修改容器元素时,它同样高效,因为它也是直接操作原元素。
    • auto (值拷贝): 效率最低,因为每次迭代都会产生一个元素的副本。只有当你确实需要副本(比如,在循环体内修改副本而不影响原容器,或者容器元素很小且拷贝开销可以忽略)时才考虑使用。

总的来说,范围for循环在大多数情况下都是高效且安全的。它的主要优势在于代码的简洁性和可读性,而不会带来显著的性能损失。

范围for循环与结构化绑定:现代C++的强强联合

C++17引入的结构化绑定(Structured Bindings)与范围for循环结合使用时,能产生极其优雅且强大的代码,尤其是在处理std::pair、std::tuple或std::map这类键值对容器时。它允许你将一个复合类型(如std::pair或std::map::value_type)的成员直接解包到独立的变量中。

我记得第一次看到结构化绑定和范围for循环这么用的时候,简直是惊艳了。以前处理map的键值对,总得写std::pair<const int std::string>& p</const>,然后p.first、p.second地访问,现在直接[key, value],代码瞬间清爽了好几个度。这感觉就像是C++终于学会了说人话,而且说得还挺优雅。

示例:遍历std::map

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载

std::map的元素类型是std::pair<const key value></const>。在没有结构化绑定之前,你需要这样遍历:

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<int, std::string> students = {{1, "Alice"}, {2, "Bob"}, {3, "Charlie"}};

    // 传统方式遍历 map
    for (const auto& pair : students) {
        std::cout << "ID: " << pair.first << ", Name: " << pair.second << std::endl;
    }
    return 0;
}

而有了结构化绑定,你可以直接将pair解包成key和value:

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<int, std::string> students = {{1, "Alice"}, {2, "Bob"}, {3, "Charlie"}};

    // 使用结构化绑定遍历 map (推荐)
    for (const auto& [id, name] : students) { // id和name直接绑定到pair的first和second
        std::cout << "ID: " << id << ", Name: " << name << std::endl;
    }

    // 如果需要修改值,可以使用非const引用
    for (auto& [id, name] : students) {
        if (id == 2) {
            name = "Bobby"; // 修改了map中的值
        }
    }
    std::cout << "修改后:" << std::endl;
    for (const auto& [id, name] : students) {
        std::cout << "ID: " << id << ", Name: " << name << std::endl;
    }
    return 0;
}

这种写法不仅更简洁,而且变量名id和name更具描述性,提高了代码的可读性。结构化绑定同样适用于数组、结构体以及std::tuple等类型,只要它们满足特定的结构化绑定要求。

范围for循环的局限性与替代方案的思考

虽然范围for循环极大地简化了代码,但它并非万能,也存在一些局限性。理解这些局限性,并知道何时选择其他遍历方式,对于编写健壮且高效的C++代码至关重要。我们不能因为它好用就无脑用,任何工具都有它的边界。我曾经就犯过在范围for循环里删除元素的错误,结果程序直接崩了,或者行为异常,调试了半天才发现是迭代器失效的问题。所以,理解它的局限性,比单纯学会怎么用,可能更重要。

局限性:

  1. 无法直接获取元素索引: 范围for循环的设计哲学是关注元素本身,而非它们在容器中的位置。如果你在循环中需要知道当前元素的索引,范围for循环无法直接提供。

    • 替代方案:

      • 手动计数: 在循环外部维护一个计数器。

        std::vector<int> data = {10, 20, 30};
        int index = 0;
        for (const auto& val : data) {
            std::cout << "Element at index " << index << ": " << val << std::endl;
            index++;
        }
      • C++23 std::views::enumerate: 如果你使用C++23或更高版本,std::views::enumerate可以优雅地解决这个问题。

        #include <iostream>
        #include <vector>
        #include <ranges> // For std::views::enumerate
        
        int main() {
            std::vector<int> data = {10, 20, 30};
            for (const auto& [index, val] : std::views::enumerate(data)) {
                std::cout << "Element at index " << index << ": " << val << std::endl;
            }
            return 0;
        }
  2. 不能在循环体内安全地修改容器大小(添加或删除元素): 这是最常见且危险的陷阱。在范围for循环中添加或删除元素可能会导致迭代器失效,进而引发未定义行为(程序崩溃、数据损坏等)。范围for循环在内部使用了begin()和end()迭代器来确定循环范围,这些迭代器在容器大小改变时可能不再有效。

    • 替代方案:

      • 传统迭代器循环: 当需要删除元素时,使用传统的迭代器循环,并小心处理迭代器失效。

        #include <iostream>
        #include <vector>
        #include <algorithm> // For std::remove_if
        
        int main() {
            std::vector<int> nums = {1, 2, 3, 4, 5, 6};
            // 错误示例:在范围for循环中删除元素
            // for (auto num : nums) {
            //     if (num % 2 == 0) {
            //         // nums.erase(std::remove(nums.begin(), nums.end(), num), nums.end()); // 错误!迭代器失效
            //     }
            // }
        
            // 正确方式1:使用传统迭代器循环进行删除
            for (auto it = nums.begin(); it != nums.end(); ) {
                if (*it % 2 == 0) {
                    it = nums.erase(it); // erase返回下一个有效迭代器
                } else {
                    ++it;
                }
            }
            std::cout << "删除偶数后: ";
            for (int n : nums) {
                std::cout << n << " "; // 输出 1 3 5
            }
            std::cout << std::endl;
        
            // 正确方式2:使用erase-remove idiom (通常结合std::remove_if)
            std::vector<int> more_nums = {1, 2, 3, 4, 5, 6};
            more_nums.erase(std::remove_if(more_nums.begin(), more_nums.end(),
                                           [](int n){ return n % 2 == 0; }),
                            more_nums.end());
            std::cout << "使用erase-remove idiom删除偶数后: ";
            for (int n : more_nums) {
                std::cout << n << " "; // 输出 1 3 5
            }
            std::cout << std::endl;
            return 0;
        }
      • 先收集要修改的元素/索引,再统一处理: 这种方式更安全,但可能需要额外的存储空间。

  3. 无法直接反向遍历: 范围for循环总是从begin()到end()正向遍历。

    • 替代方案:
      • 传统反向迭代器循环: 使用rbegin()和rend()。
        std::vector<int> data = {1, 2, 3, 4, 5};
        for (auto it = data.rbegin(); it != data.rend(); ++it) {
            std::cout << *it << " "; // 输出 5 4 3 2 1
        }
        std::cout << std

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

超级麦吉

一款通过数字员工协作完成调研、数据分析与文档制作的AI办公平台。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

7519

2023.08.02

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6384

2023.10.24

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2458

2023.09.20

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

5434

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

5611

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

7519

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3565

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

4708

2025.08.29

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

20

2026.11.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go-Zero HTTP服务开发指南
Go-Zero HTTP服务开发指南

共0课时 | 0人学习

Go-Zero goctl安装与使用手册
Go-Zero goctl安装与使用手册

共0课时 | 0人学习

Go-Zero框架快速开始指南
Go-Zero框架快速开始指南

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn