Chapter 15 — 迭代器模式 Iterator灵魂速记电视遥控器的下一个按钮——不管频道列表怎么存的按一下就换一个。秒懂类比你在用电视遥控器按下一个→ 换到下一个频道按上一个→ 换到上一个你不需要知道频道列表存在哪里、按什么顺序排列、是数组还是链表迭代器就是这个遥控器提供一种方法按顺序访问集合中的元素但不暴露集合的内部结构。问题引入// 灾难现场直接暴露内部结构classBookShelf{public:std::vectorBookgetBooks(){returnbooks_;}// 暴露了内部实现private:std::vectorBookbooks_;};// 客户端直接依赖 vectorfor(inti0;ishelf.getBooks().size();i){autobookshelf.getBooks()[i];}// 有一天改成 std::list 存储所有客户端代码全炸// 有一天想按作者名过滤遍历在每个客户端加逻辑模式结构┌─────────────┐ ┌─────────────┐ │ Iterable │─────→│ Iterator │ │ (集合) │创建 │ (迭代器) │ ├─────────────┤ ├─────────────┤ │createIter()│ │hasNext() │ │ │ │next() │ │ │ │current() │ └─────────────┘ └─────────────┘C 实现自定义迭代器理解原理#includeiostream#includestring#includevector#includestdexcept// 集合元素 structSong{std::string title;std::string artist;};// 迭代器接口 templatetypenameTclassIterator{public:virtual~Iterator()default;virtualboolhasNext()const0;virtualTnext()0;virtualvoidreset()0;};// 集合接口 templatetypenameTclassIterableCollection{public:virtual~IterableCollection()default;virtualstd::unique_ptrIteratorTcreateIterator()0;};// 具体集合播放列表 classPlaylist:publicIterableCollectionSong{public:voidaddSong(Song song){songs_.push_back(std::move(song));}size_tsize()const{returnsongs_.size();}Songat(size_t index){returnsongs_[index];}std::unique_ptrIteratorSongcreateIterator()override;private:std::vectorSongsongs_;};// 具体迭代器顺序遍历 classPlaylistIterator:publicIteratorSong{public:explicitPlaylistIterator(Playlistplaylist):playlist_(playlist),index_(0){}boolhasNext()constoverride{returnindex_playlist_.size();}Songnext()override{returnplaylist_.at(index_);}voidreset()override{index_0;}private:Playlistplaylist_;size_t index_;};// 需要在 Playlist 类定义完之后实现std::unique_ptrIteratorSongPlaylist::createIterator(){returnstd::make_uniquePlaylistIterator(*this);}intmain(){Playlist playlist;playlist.addSong({Bohemian Rhapsody,Queen});playlist.addSong({Hotel California,Eagles});playlist.addSong({Imagine,John Lennon});playlist.addSong({Yesterday,Beatles});// 用迭代器遍历——不需要知道内部是 vector 还是 listautoitplaylist.createIterator();std::cout 播放列表 \n;while(it-hasNext()){autosongit-next();std::cout song.title - song.artist\n;}}输出 播放列表 Bohemian Rhapsody - Queen Hotel California - Eagles Imagine - John Lennon Yesterday - BeatlesC STL 风格迭代器实战推荐在 C 中你几乎不需要手写迭代器模式因为 STL 已经内置了#includeiostream#includevector#includealgorithmintmain(){std::vectorstd::stringsongs{Bohemian Rhapsody,Hotel California,Imagine};// range-based for底层用的就是迭代器for(constautosong:songs){std::cout song\n;}// 显式使用迭代器for(autoitsongs.begin();it!songs.end();it){std::cout *it\n;}// 用算法 迭代器autofoundstd::find(songs.begin(),songs.end(),Imagine);if(found!songs.end()){std::cout找到了: *found\n;}}让自定义类支持 range-based forC 的for (auto x : container)语法不需要你的类继承什么接口只需要满足两个条件提供begin()和end()方法或全局的begin()/end()函数返回的迭代器支持三个操作*取值、前进、!判断是否到头classNumberRange{public:NumberRange(intstart,intend):start_(start),end_(end){}structIterator{intvalue;intoperator*()const{returnvalue;}// 取当前值Iteratoroperator(){value;return*this;}// 前进一步booloperator!(constIteratorother)const{// 还没到头returnvalue!other.value;}};Iteratorbegin()const{return{start_};}// 起点Iteratorend()const{return{end_};}// 终点不包含private:intstart_,end_;};// 现在可以直接用 range-based forfor(intn:NumberRange(1,6)){std::coutn ;// 1 2 3 4 5}编译器看到for (int n : range)时会展开成auto__beginrange.begin();auto__endrange.end();for(;__begin!__end;__begin){intn*__begin;// 循环体}所以*、、!三个运算符缺一不可。什么时候用✅ 适合❌ 别用需要统一遍历不同结构的集合std::vector range-for 就够了需要多种遍历方式正序/逆序/过滤只需要简单遍历自定义数据结构需要遍历用标准容器直接搞定隐藏集合内部实现内部实现无所谓暴露在 C 中99% 的情况用 STL 迭代器就够了。只有自定义复杂数据结构时才需要手写。防混淆Iterator vs VisitorIteratorVisitor做什么遍历集合元素对元素施加操作关注点访问顺序操作逻辑配合经常一起用经常一起用一句话分清Iterator 管一个一个取出来Visitor 管取出来后干什么。Iterator vs Composite经常配合使用Composite 定义树形结构Iterator 定义如何遍历它深度优先广度优先。现代 C 小贴士C20 RangesC20 引入的Ranges 库是迭代器模式的终极形态。它解决了传统 STL 迭代器的几个痛点#includeranges#includevector#includeiostreamintmain(){std::vectorintnums{1,2,3,4,5,6,7,8,9,10};// 旧写法两个迭代器作参数verbosestd::vectorintresult;for(autox:nums){if(x%20)result.push_back(x*x);}// C20 Ranges链式管道懒计算autoviewnums|std::views::filter([](intx){returnx%20;})// 过滤偶数|std::views::transform([](intx){returnx*x;});// 平方for(intx:view)std::coutx ;// 4 16 36 64 100}Ranges 的三大优势管道语法|像 shell 的管道一样链式组合操作直观懒求值views::filter和views::transform不会立刻产生临时容器只有遍历时才计算概念约束std::ranges::rangeConcept 取代了手写的begin()/end()编译器给出更清晰的错误信息自定义类型若满足std::ranges::rangeconcept提供begin()和end()且返回的迭代器满足相应要求就能无缝接入所有 ranges 算法。