Closed Ocxs closed 9 years ago
vector<int> ivec(istream_iterator<int> (cin), istream_iterator<int> (eof));
这个eof
是什么呢?我查了下 istream_iterator<T>
的构造函数,它只接受 std::basic_istream<CharT, Traits>
类型的参数呀。
凭空出来个 eof
,很不解。我在 Clang 里试了下,都无法通过编译。
你说 VS 中没报错,我手头没有,明天再帮你看看吧。如果真的不报错,那真搞不懂是什么黑魔法了。。
@pezy 我vector<int> ivec(istream_iterator<int> (cin), istream_iterator<int> (eof));
这句写错了。
因为书上有这种写法:
istream_iterator<int> in_iter(cin), eof;
vector<int> ivec(in_iter,eof);
我想将这个合成一句话,像你的那句std::copy(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "));
这样写在一起,而不是先std::ostream_iterator<int> out_iter(std::cout, " ");
,再把out_iter传进去。
怎么才能做到将这两句合成一句呢?
istream_iterator<int> in_iter(cin), eof;
vector<int> ivec(in_iter,eof);
std::vector<int> vec((std::istream_iterator<int>(std::cin)), std::istream_iterator<int>());
@pezy 难怪我之前一直报错,原来少加了一个括号,可是为什么要在这(std::istream_iterator<int>(std::cin)
加一个括号呢?
@Ocxs 这是 C++ 里面一个有名的坑。叫做:Most vexing parse,说白了,就是你这么写会让编译器费解,不确定这是在声明一个对象,还是在声明一个函数。当然,我加了个括号,其实是 C++0x 里的避嫌做法,貌似是Effective C++ 里讲的做法。
在 C++11 里,其实更好的写法是这样:
std::vector<int> vec{std::istream_iterator<int>(std::cin), std::istream_iterator<int>()};
这样就不会让你迷惑了吧?
对于wiki里的例子
TimeKeeper time_keeper(Timer());
我倒是可以理解,可以理解成变量或者函数,导致歧义。 1.但是在这次问的问题中,根据这篇资料,如果我没理解错的话,主要其中的这句话
给形参声明加上括号不合法,但给函数调用的实参加括号是合法的.
结合你刚才的例子std::vector<int> vec((std::istream_iterator<int>(std::cin)), std::istream_iterator<int>());
你给其参数加了括号,(由于形参声明加括号是非法的),所以编译器为了让这句合法,就会主动的将其解释为变量声明,声明一个类型为vector<int>
的变量vec
,而不是类型为vector<int>
的函数vec
。但是也如那篇博客里所说有的编译器可能不支持这种写法,所以有可能会报错,不过这里我们用的vs2013是支持的,所以不会报错?(我理解的对否?@pezy )
@Ocxs Wiki 只是简化问题凸显问题的本质。举一反三,这里是一样的。
你引用的资料基本就来自 Effective STL, 你基本上也理解到了。博客里说的可能不支持,不必担心,那是针对较老的编译器而言。正如博客里还说,“这是运行期的错误”,而当前主流编译器,都会将其处理为编译期错误。
但我依然建议不要那么写,用大括号更加利于理解。
另外:建议看 C++ Primer 的时候不要思考的过细,因为这都是实践中才会遇到的问题。Effective 系列会对这些问题更有体系的讲解,目前你还体会不到这个括号的妙处。
@pezy 其实我觉得大括号不容易理解啊,因为我们传入的是一个迭代器范围,而用迭代器范围初始化都是用()
,如vector<int> ivec(vec.begin(),vec.end());
用{}
都是具体的元素,如vector<int> ivec{1,2,3};
不过书上3.3节说了,如果初始化使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector
对象,所以
std::vector<int> vec{std::istream_iterator<int>(std::cin), std::istream_iterator<int>()};
由于std::istream_iterator<int>(std::cin)
不能用来列表初始化,所以才用它来构造vec
?
@Ocxs 不好意思,我说的不准确。应该是,用大括号更直观。你想嘛,平白无故多个括号,乍一看不觉得奇怪吗?
然后我再来详细说说大括号的好处:
再深入一步,initializer_list 的本质是什么?我们观察一下其 member, 发现非常简单,主要就是两个迭代器:begin
和 end
。所以,都不需要看实现代码,就能知道这货本质就是一个 iterator range.
那么:
{1, 2, 3, 4}
会进行怎样的解析呢?begin
指向 1
, end
指向 4
后面一位。即:
1 2 3 4 _
^ ^
begin end
好了,上述都好理解吧?我们再看,如果 {}
里放的不是值,而是迭代器,如:
{beg, end}
会进行怎样的解析呢?其实还是一样:
beg end
^ ^
beg end
看到了吗?无论你给 {}
里面放了啥东西,在它看来,都是头尾迭代器罢了。
这就是本质。
反观书上:
如果初始化使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象。
这句话听起来像是某种规则,啰里八嗦的又臭又长。
不要记什么规则,你从本质去看,就会非常清晰。纠结于字眼完全没有任何意义。
1 2 3 4 _
^ ^
beg end
^ ^
begin end1
就这个例子而言
也就是说 : std::initializer_list的begin 指向beg,而beg所指向的是1,
std::initializerlist的end1指向 end,end所指向的是。
@Ocxs 就是这样!
@pezy Thx!:+1: 其实还有点小地方不懂,不过已经脱离这个范畴了,等学深入了在来理解理解。:smile:
@Ocxs 没关系,哪里不懂直接说。反正这早就偏离基础范畴了。
@pezy 比如
1 2 3 4 _
^ ^
begin end
我猜是while(begin!=end){++begin;}
这种方式来构造vec,但是如果是
1 2 3 4 _
^ ^
beg end
^ ^
begin end1
该怎么传递这个迭代器范围来构造vec呢?
先来一段简单的测试程序:
#include <iostream>
#include <vector>
#include <initializer_list>
int main()
{
std::initializer_list<int> il{ 1, 2, 3, 4 }; // [1]
std::vector<int> vec{ il.begin(), il.end() }; // [2]
for (auto i : vec) std::cout << i << " ";
std::cout << std::endl;
}
initializer_list(const _Elem *_First_arg, const _Elem *_Last_arg) _NOEXCEPT : _First(_First_arg), _Last(_Last_arg)
{ // construct with pointers
}
可以看到构造函数啥也没干,只是单纯如我刚才所说,将首尾指针赋值。
你肯定要问,明明是 {}
,怎么跑到构造里,变成了 const _Elem *_First_arg
和 const _Elem *_Last_arg
了?这一段转换过程,VS 是用汇编实现的:
010CF9B4 add dword ptr [eax],eax
010CF9B6 add byte ptr [eax],al
010CF9B8 add al,byte ptr [eax]
010CF9BA add byte ptr [eax],al
010CF9BC add eax,dword ptr [eax]
010CF9BE add byte ptr [eax],al
010CF9C0 add al,0
010CF9C2 add byte ptr [eax],al
估计你没接触过汇编,我就简单给你说下这个过程的步骤:
底层拿到 {1, 2, 3, 4}
,实际是一个压栈的过程,_First_arg
一直指向栈底,_Last_arg
一直指向栈口。读一个数,压入栈,直到最后,画出来,如下图:
|_0xcccc_| <- _Last_arg (pointer)
|_0x0100_|
|_0x0011_|
|_0x0010_|
|_0x0001_| <- _First_arg (pointer)
所以在上层 C++ 里,看到的就直接是两个指针了。再经过一些封装,就是我们所谓的迭代器。
std::initializer_list
的首尾迭代器到 std::vector
的构造,这个过程 VS 为了优化效率,封装的比较深,过程有些繁琐,所以我简化了步骤,给你讲几个关键的点。这个构造的过程,实际是靠 std::allocator 来完成的,这个类你会在第 12 章里面接触到。他的作用总结来说,就是分配内存。
那么首先就是分配多大,我们有了首尾迭代器,就知道长度,那么调用 allocate
方法就可以开辟出一段这么大的内存来。
然后,就是赋值。通过 construct
方法类似你给出的那个 while 循环,逐一赋值即可。
@pezy 谢谢,你这句话说了:“将首尾指针赋值”,我就理解大半了,不过我看我vs反汇编的代码好像跟你有点不一样,我的反汇编代码我觉得更容易理解一点
std::vector<int> vec{ il.begin(), il.end() }; // [2]
00908960 push 8
00908962 lea ecx,[il]
00908965 call std::initializer_list<int>::__autoclassinit2 (090157Dh)
0090896A mov dword ptr [ebp-34h],1
00908971 mov dword ptr [ebp-30h],2
00908978 mov dword ptr [ebp-2Ch],3
0090897F mov dword ptr [ebp-28h],4
00908986 lea eax,[ebp-24h]
00908989 push eax
0090898A lea ecx,[ebp-34h]
0090898D push ecx
0090898E lea ecx,[il]
00908991 call std::initializer_list<int>::initializer_list<int> (09012D5h)
先把1234存入连续的内存单元,然后将迭代器end和begin压入栈。
@Ocxs :+1:
@pezy Thx! :smile:
我看@pezy 的答案用了这种写法,
然后我又尝试了这种写法,
此种方法在vs2013中并没有报错,但是当我加上
sort(ivec.begin(), ivec.end());
时,就会报错,看报错的原因应该是ivec的原因,但是不知道为什么?书上这一节有个从迭代器范围构造vec的写法,不过书上是这么写的: