导言
范围循环(C++11 引入)是现代 C++ 中遍历容器的便捷方式,其核心依赖迭代器协议与begin/end 接口。
一、范围循环的底层实现原理
C++ 标准规定,对于表达式for (range_declaration : range_expression),编译器会自动将其展开为以下逻辑(伪代码):
1 2 3 4 5 6 7 8 9
| // 1. 获取范围的起始与结束迭代器 auto __begin = begin(range_expression); auto __end = end(range_expression);
// 2. 遍历逻辑:依赖迭代器的 !=、++、* 操作 for (; __begin != __end; ++__begin) { range_declaration = *__begin; // 解引用获取元素 loop_statement; // 循环体 }
|
关键依赖接口
要支持范围循环,自定义对象需满足:
存在可被调用的 begin() 和 end() 函数(成员函数或非成员函数);
begin()/end() 返回的迭代器对象需支持以下操作:
二、迭代器协议的设计与实现
迭代器本质是 “封装遍历逻辑的对象”,其设计需贴合容器的存储结构(连续存储 / 链式存储等)。以下以连续存储的自定义容器为例,设计符合标准的迭代器。
2.1 迭代器类的核心结构
以 “动态整型数组容器”IntArray的迭代器IntArrayIterator为例,实现步骤如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <cstddef> // 用于size_t
// 前置声明容器类,因为迭代器需要访问容器的私有成员 class IntArray;
// 自定义迭代器类:支持正向遍历 class IntArrayIterator { public: // -------------------------- 1. 迭代器类型别名(兼容STL算法,推荐)-------------------------- using value_type = int; // 迭代器指向元素的类型 using pointer = int*; // 元素指针类型 using reference = int&; // 元素引用类型 using difference_type = std::ptrdiff_t; // 两个迭代器间的距离类型 using iterator_category = std::forward_iterator_tag; // 迭代器类别(正向迭代器)
// -------------------------- 2. 构造函数 -------------------------- // 接收容器的当前位置指针(核心:迭代器本质是“带逻辑的指针”) explicit IntArrayIterator(pointer ptr) : m_ptr(ptr) {}
// -------------------------- 3. 核心操作符重载 -------------------------- // 1. 解引用:返回当前元素的引用(支持修改元素) reference operator*() const { return *m_ptr; }
// 2. 箭头操作符:支持通过迭代器访问元素成员(若元素是对象) pointer operator->() const { return m_ptr; }
// 3. 前缀自增:移动到下一个元素,返回更新后的迭代器 IntArrayIterator& operator++() { ++m_ptr; // 指针移动(连续存储的核心逻辑) return *this; }
// 4. 后缀自增(可选,范围循环不直接依赖,但为完整性实现) IntArrayIterator operator++(int) { IntArrayIterator temp = *this; // 保存当前状态 ++m_ptr; // 移动指针 return temp; // 返回旧状态 }
// 5. 不等于比较:判断是否到达遍历终点 friend bool operator!=(const IntArrayIterator& lhs, const IntArrayIterator& rhs) { return lhs.m_ptr != rhs.m_ptr; }
// (可选)等于比较:为完整性实现 friend bool operator==(const IntArrayIterator& lhs, const IntArrayIterator& rhs) { return lhs.m_ptr == rhs.m_ptr; }
private: pointer m_ptr; // 核心成员:指向当前元素的指针(连续存储场景) };
|
2.2 迭代器设计要点
迭代器类别:std::forward_iterator_tag 表示正向迭代器,若需支持反向遍历,需实现 std::bidirectional_iterator_tag 并添加 -- 操作;
引用返回:operator* 返回引用(int&)而非值,避免元素拷贝,同时支持通过迭代器修改容器元素;
友元函数:operator!= 设为友元,方便访问私有成员 m_ptr(若迭代器成员是公有的,也可改为成员函数)。
三、自定义容器实现(支持范围循环)
基于上述迭代器,实现一个简单的动态整型数组容器 IntArray,核心是提供 begin() 和 end() 成员函数。
3.1 容器完整实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| #include <stdexcept> // 用于std::out_of_range #include <utility> // 用于std::move
class IntArray { public: // -------------------------- 1. 容器类型别名(关联迭代器)-------------------------- using iterator = IntArrayIterator; // 普通迭代器 using const_iterator = const IntArrayIterator; // const迭代器(下文扩展)
// -------------------------- 2. 构造/析构/拷贝控制(三法则)-------------------------- // 构造函数:初始化指定大小的数组 explicit IntArray(size_t size) : m_size(size), m_data(new int[size]()) {}
// 析构函数:释放动态内存 ~IntArray() { delete[] m_data; }
// 拷贝构造函数:深拷贝(避免浅拷贝导致的内存泄漏) IntArray(const IntArray& other) : m_size(other.m_size), m_data(new int[other.m_size]) { for (size_t i = 0; i < m_size; ++i) { m_data[i] = other.m_data[i]; } }
// 拷贝赋值运算符:深拷贝 IntArray& operator=(const IntArray& other) { if (this != &other) { // 避免自赋值 // 先释放当前内存,再分配新内存并拷贝 delete[] m_data; m_size = other.m_size; m_data = new int[m_size]; for (size_t i = 0; i < m_size; ++i) { m_data[i] = other.m_data[i]; } } return *this; }
// (可选)移动构造与移动赋值:提升性能(C++11) IntArray(IntArray&& other) noexcept : m_size(other.m_size), m_data(other.m_data) { other.m_size = 0; other.m_data = nullptr; // 避免被析构函数重复释放 }
IntArray& operator=(IntArray&& other) noexcept { if (this != &other) { delete[] m_data; m_size = other.m_size; m_data = other.m_data; other.m_size = 0; other.m_data = nullptr; } return *this; }
// -------------------------- 3. 核心:范围循环依赖的begin/end -------------------------- // 普通迭代器:支持修改元素 iterator begin() { return iterator(m_data); // 返回指向第一个元素的迭代器 }
iterator end() { return iterator(m_data + m_size); // 返回指向“尾后位置”的迭代器 }
// const迭代器:不支持修改元素(用于const容器) const_iterator begin() const { return const_iterator(m_data); }
const_iterator end() const { return const_iterator(m_data + m_size); }
// (C++11)cbegin/cend:显式返回const迭代器(兼容STL习惯) const_iterator cbegin() const { return begin(); }
const_iterator cend() const { return end(); }
// -------------------------- 4. 辅助接口(可选)-------------------------- // 元素访问:支持下标操作 int& operator[](size_t index) { if (index >= m_size) { throw std::out_of_range("IntArray: index out of range"); } return m_data[index]; }
const int& operator[](size_t index) const { if (index >= m_size) { throw std::out_of_range("IntArray: index out of range"); } return m_data[index]; }
size_t size() const { return m_size; }
private: size_t m_size; // 数组大小 int* m_data; // 动态数组指针(连续存储) };
|
3.2 容器实现核心要点
begin/end 的语义:begin() 返回指向第一个元素的迭代器,end() 返回指向尾后位置(即最后一个元素的下一个位置)的迭代器,这是 C++ 迭代器的 “左闭右开” 原则;
const 迭代器:const_iterator 需确保解引用后返回const int&,避免修改元素。通过重载const版本的begin()/end(),支持const IntArray对象的范围循环;
内存安全:严格遵循 “三法则”(析构、拷贝构造、拷贝赋值),避免动态内存泄漏;添加移动语义(C++11)可提升性能。
四、编译验证与测试案例
提供完整的可编译代码,验证自定义容器的范围循环功能,测试场景包括:普通遍历、修改元素、const 容器遍历、边界情况(空容器)。
4.1 测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> #include "IntArray.h" // 假设上述容器和迭代器代码在IntArray.h中
int main() { // 测试1:普通容器的范围循环(修改元素) IntArray arr(5); for (size_t i = 0; i < arr.size(); ++i) { arr[i] = i * 10; // 初始化元素:0, 10, 20, 30, 40 }
std::cout << "测试1:修改元素的范围循环\n"; for (auto& val : arr) { // auto& 支持修改元素 val += 5; // 元素变为:5, 15, 25, 35, 45 std::cout << val << " "; } std::cout << "\n"; // 输出:5 15 25 35 45
// 测试2:const容器的范围循环(不可修改元素) const IntArray const_arr = arr; // const容器 std::cout << "测试2:const容器的范围循环\n"; for (const auto& val : const_arr) { // const auto& 不可修改 std::cout << val << " "; } std::cout << "\n"; // 输出:5 15 25 35 45
// 测试3:空容器(边界情况) IntArray empty_arr(0); std::cout << "测试3:空容器的范围循环(无输出)\n"; for (auto& val : empty_arr) { std::cout << val; // 不会执行 }
// 测试4:兼容STL算法(依赖迭代器类型别名) #include <algorithm> // 用于std::for_each #include <functional> // 用于std::cout std::cout << "测试4:兼容STL算法(std::for_each)\n"; std::for_each(arr.begin(), arr.end(), [](int val) { std::cout << val * 2 << " "; // 输出:10 30 50 70 90 }); std::cout << "\n";
return 0; }
|
4.2 预期输出:
1 2 3 4 5 6 7
| 测试1:修改元素的范围循环 5 15 25 35 45 测试2:const容器的范围循环 5 15 25 35 45 测试3:空容器的范围循环(无输出) 测试4:兼容STL算法(std::for_each) 10 30 50 70 90
|