07 | 迭代器和好用的新for循环
07 | 迭代器和好用的新for循环
讲述:吴咏炜
时长11:44大小8.05M
什么是迭代器?
常用迭代器
使用输入行迭代器
定义输入行迭代器
内容小结
课后思考
参考资料
赞 4
提建议
精选留言(34)
- 小一日一2019-12-11看了老师的代码再看自己学的代码,感觉我的C++是小学生水平。 以为自己看过几遍C++ PRIMER 5th, 看过并理解effective++, more effective c++, inside the c++ object model, 能应付平时的开发需要,也能看懂公司别人的代码,就觉得自己的C++不错了,看了老师github的代码后我是彻底服了,感叹C++太博大精深,永远不敢说自己精通C++。 我什么时候才能达到老师对C++理解并使用的高度呢,难道也需要20年么?展开
作者回复: 肯定还有更好的 C++ 代码的。学习无止境! 认真学习,应该不用那么久(我还没有极客时间专栏来帮助我学习呢😌)。 反过来,说明老程序员还有点价值么。🤗
共 3 条评论28 - Geek_b68b742020-01-171、使用输入行迭代器 这一部分里,“ auto&& r = istream_line_reader(is);” 这里为什么要用右值引用呢? 2、还是使用输入行迭代器这里, for (const string& line : istream_line_reader(is)) { // 示例循环体中仅进行简单输出 cout << line << endl; } “获取冒号后边的范围表达式的结果,并隐式产生一个引用,在整个循环期间都有效。注意根据生命期延长规则,表达式结果如果是临时对象的话,这个对象要在循环结束后才被销毁。” 第一句是说line在整个循环期间有效?这是想表达什么呢?还有第二句,指的是哪个临时对象呢?在哪个循环结束后销毁呢?期待您的解答展开
作者回复: 你的两个疑问实际是针对同一个地方。 auto&& 那句是用一个“万能”引用捕获一个对象,左值和右值都可以。C++ 的生命期延长规则,保证了引用有效期间,istream_line_reader 这个“临时”对象一直存在。没有生命期延长的话,临时对象在当前语句执行结束后即销毁。
共 3 条评论7 - 千鲤湖2019-12-18过来看看老师问的那两个问题,好奇中。。。
作者回复: 公布第 1 个问题的答案吧: #include <fstream> #include <iostream> #include "istream_line_reader.h" using namespace std; int main() { ifstream ifs{"test.cpp"}; istream_line_reader reader{ifs}; auto begin = reader.begin(); for (auto it = reader.begin(); it != reader.end(); ++it) { cout << *it << '\n'; } } 以上代码,因为 begin 多调用了一次,输出就少了一行……
6 - Geek_d890792022-01-02我可能学的是C--4
- nelson2019-12-12如果stream_是nullptr会怎么样?
作者回复: 得到一个空的不能遍历的迭代器。跟任何 end() 相等比较返回真,因而你不可以对它做 ++ 操作。如果你要硬来,它就死给你看。
共 2 条评论3 - EncodedStar2019-12-24课后思考 1.目前这个输入迭代器的行为,在干什么情况下可能导致意料之外的后果? 答:目前这个输入迭代器在构造里调用了++,所以,多一次构造就可能读到意料之外的结果了。 2.请尝试一下改进这个输入行迭代器,看看能不能消除这种意外,如果可以,该怎么做?如果不可以,为什么? 答:可以啊,文章里提到了,这个输入行迭代器构造的使用了++,是为了与日常使用一致,如果想改进这个一块,我们也可以改构造的时候展开
作者回复: 1 对。2 你需要自己实验一下,再想想会不会有其他副作用。
2 - 旭东2019-12-14老师,您好,iterater中后置++的实现是不是应该返回const;避免(i++)++这样的代码通过编译?
作者回复: 1. 不能写 const,因为你修改了自己。 2. 就算能写也防不了,因为你返回的是个全新的对象。
2 - 禾桃2019-12-11输入迭代器和输出迭代器, 这个入和出是相对于什么而言的? 感觉有点绕。 谢谢!
作者回复: cout << *it 就是读; *it = 42 就是写。
3 - 晚风·和煦2019-12-11从 C++17 开始,I 和 S 可以是不同的类型。这带来了更大的灵活性和更多的优化可能性。 没太理解这句话😂
作者回复: 现在 r.begin() 和 r.end() 可以是不同类型了。
2 - Slience-0°C2022-04-21有个问题请教老师,工作中看到基于范围的for循环中,使用了auto &&来获取数据,而不是auto&,有啥区别么?难道是为了使用移动构造函数?伪代码如下:std::vectors<std::string> vec; for (auto&& : vec)
作者回复: auto&& 是转发引用(第 8 讲也会简单提到)。转发引用的概念在第 3 讲里提过(我可能会在第 37 讲再展开探讨一下),但在那里的形式是 T&&。 auto&& 能匹配所有的引用(const 左值、非 const 左值、右值等),使用正确的场景下后续应该使用 std::forward。如果你没看到后续用 std::forward,那这个使用很有可能不是最好的写法。根据你是否需要修改 vec 中的内容,auto& 或 const auto& 会更加明确。
共 6 条评论2 - doge2021-02-24第二个问题想了半天,好像做不到,根据begin的语义,拿到stream_对象后就必须取得第一行内容,否则返回的就是一个空string而不是文件的第一行。但是在iterator对象内好像没办法记录“第一次从strem_读”这样的一个状态。我尝试标记第一次,但是会忽视读的操作,这样还是会导致第一行内容的丢失。希望老师解惑。 explicit iterator(istream& is) : stream_(&is) {} iterator begin() { cout << "first_ = " << first_ << endl; if (first_) { first_ = false; return ++iterator(*stream_); } else { return iterator(*stream_); } }展开
作者回复: 对的,这题的目的就是让你理解目前这种做法实际还是个不错的折中。也可以参考一下参考答案那节。
1 - englefly2020-03-13"从 C++17 开始,I 和 S 可以是不同的类型" 意味着 " r.begin() 和 r.end() 可以是不同类型了。" 那么常见的循环遍历是不是就有问题了?比如下代码, it = r.begin() 此时 it是r.begin() 的类型,但it还要和r.end()比较,这时就是两个不同类型在比较了 auto it = r.begin(); auto end = r.end(); for (; it != end; ++it) {...}展开
作者回复: 目的就是要允许 it 和 end 是不同类型…… 当然,这两个类型之间是需要允许比较操作的。后面有个例子,你可以先看一下: https://github.com/adah1972/geek_time_cpp/blob/master/29/test05_null_sentinel.cpp 要点是,这种情况下,比较的时候可以不是比较迭代器本身,而是做一下更复杂的操作。如检查文件是否结束,读取的内容是否为 NUL(例子里的情况),甚至比较永远失败(无限循环的情况)。
1 - robonix2020-01-02老师,iterator begin()函数返回一个iterator对象,这个对象还包含了string成员,这样就得拷贝了吧,效率会不会不高呢
作者回复: 好问题。不过,你和getline的版本仔细分析比较一下的话,也是一个string,大家半斤八两。毕竟,按我们的用法,一个流上你也只会用一个有效的istream_line_reader::iterator。另外,遍历的时候我用的是const string&,没有额外的拷贝。
1 - 总统老唐2019-12-16吴老师,这一课有两个疑问: 1,“到底应该让 * 负责读取还是 ++ 负责读取”,该怎样理解?如果“读取”指的是在istream上读取一行,放入line_成员中,用++实现这个操作是最常见和直觉的,同时,用 * 返回读取的内容也在最容易想到的方式,反过来,什么情况下会需要”用*来负责读取“? 2,输入迭代器为什么要定义 iterator operator++(int)
作者回复: 1. 用 ++ 是最合理的,但也有一个奇怪的地方,目前还没人说到。 2. 这个就是后置 ++。迭代器要求前置和后置 ++ 都要定义,虽然我目前只使用了前置版本。
1 - 我不生产bug,我只是b...2019-12-13遍历一遍后,第二次调用begin会崩溃,stream_指针已经为空
作者回复: 作为input iterator,本来你就不应该遍历第二次的。这个不是问题。
1 - Geek_71d4ac2019-12-11在构造函数中使用this是否安全?万一构造中途失败了呢?
作者回复: 本身没有任何问题。如何保证行为安全(如异常安全)是个独立问题,跟是否在构造函数里没啥关系。尽量不使用裸指针非常重要,用了的话就需要照顾很多细节了……
1 - 硕2022-03-29写得真巧妙。佩服佩服!1
- 水月2022-03-10看了老师关于Python yield的那篇博客,体会非常深刻。一些小算法虽然用python确实慢,但是函数式编程配上yield+yield from能给代码简化到非常不人道的水平。非常期待C++也能把这些程序员友好型设计尽快收进来
作者回复: 等看到第30讲,再配合一点开源库,还是很香的: https://github.com/andreasbuhr/cppcoro https://github.com/netcan/asyncio
- 怪兽2021-06-14对于问题2,既然输入迭代器不禁止调用begin多次,那是否可以在每次调用begin时,重置输入流的位置到开头?不也正好符合begin的语义?
作者回复: 输入流是 std::cin 你怎么办?
- zhengfan2020-07-14吴老师您好。 我对于对于后置++的实现有些疑问。 如果通过后置++获得了Itr并解引用,string内容其实已经是经过++的了吧?
作者回复: 不会。后置++是先把整个iterator,包括其中的string,复制了一份。只用*的话,就是访问原先的内容。 当然,这个后置++复制了整个string,效率不高。不必要的话,不要使用。
共 3 条评论