wwwtc6answer.htm
来自「C++builder学习资料C++builder」· HTM 代码 · 共 979 行 · 第 1/3 页
HTM
979 行
return value is not used, for *any* data type. On some platforms,
it's even faster for integers. Unless someone overloads the
prefix operator in a nonsensical way, it's always as fast or faster
than postfix.
</P>
<P>
<TABLE WIDTH="75%">
<TR>
<TD VALIGN="top">
<IMG SRC="images/exclamation.gif" ALT="Tip" BORDER=0 HSPACE="0" ALIGN="top" width="32" height="48">
</TD>
<TD valign="top">
<b>Tip:</b>
<hr size = 1>
Prefer <TT>++i</TT> over <TT>i++</TT> whenever possible. I think the name of
the language, C++, is partially to blame for the over use of postfix
operator.
<hr size = 1>
</TD>
</TR>
</TABLE>
<P>
Next, since <TT>v</TT> is a const reference, it must use a <TT>const_iterator</TT> or
it will not compile. For read-only operations, using <TT>const_iterator</TT> is
always the best choice. Const is a safety measure. Most
professionals use safety equipment: football players wear pads,
doctors wear latex gloves, and (good) computer programmers use const
whenever possible. After all, it's crazy to enter a construction
yard without a hard-hat.
</P>
<P>
Now for the main point of this exercise. This question was designed to point
out that the standard library supplies a great deal of functionality
that many people do not use. Consider the <TT>count_if()</TT> algorithm to
implement this function, since it does what the programmer wants,
straight out of the box.
</P>
<P>
<TABLE WIDTH="75%">
<TR>
<TD VALIGN="top">
<IMG SRC="images/exclamation.gif" ALT="Tip" BORDER=0 HSPACE="0" ALIGN="top" width="32" height="48">
</TD>
<TD valign="top">
<b>Tip:</b>
<hr size = 1>
Use the standard library as often as possible. If you don't know what's in the standard library, it's worth
the time to learn. The upfront cost of learning what is available
is more than paid for when you can actually make use of the standard
library. It's tested, efficient, correct, and standardized.
<hr size = 1>
</TD>
</TR>
</TABLE>
<P>
A better way to have written this function, then would be to use the
standard library algorithm <TT>count_if()</TT>.
</P>
<pre>
<font color="navy">// count all a's in the range that are "special"</font>
<b>extern</b> <b>bool</b> is_special_value<b>(</b>A <b>*</b><b>)</b><b>;</b>
<b>int</b> count_special_values<b>(</b><b>)</b>
<b>{</b>
<b>return</b> std<b>:</b><b>:</b>count_if<b>(</b>v<b>.</b>begin<b>(</b><b>)</b><b>,</b> v<b>.</b>end<b>(</b><b>)</b><b>,</b> is_special_value<b>)</b><b>;</b>
<b>}</b>
</pre>
<P>
What are the chances of this new version being buggy? Practically
zero. What are the chances of accidentally using an inefficient
operation? Practically zero, since you write no code. Of course,
the existence of <TT>count_if()</TT> almost eliminates the need for
<TT>count_special_values()</TT> altogether.
</P>
<P>
<TABLE WIDTH="75%">
<TR>
<TD VALIGN="top">
<IMG SRC="images/exclamation.gif" ALT="Tip" BORDER=0 HSPACE="0" ALIGN="top" width="32" height="48">
</TD>
<TD valign="top">
<b>Tip:</b>
<hr size = 1>
Avoid writing home-brew algorithms whenever possible because doing
so will often turn out to be a waste of your time.
<hr size = 1>
</TD>
</TR>
</TABLE>
<P>
<B>Question #6:</B>
</P>
<pre>
<font color="navy">// erase all members of v1</font>
<b>void</b> f<b>(</b><b>)</b>
<b>{</b>
V<b>:</b><b>:</b>iterator i<b>;</b>
V<b>:</b><b>:</b>iterator end <b>=</b> v1<b>.</b>end<b>(</b><b>)</b><b>;</b>
<b>for</b> <b>(</b>i<b>=</b> v1<b>.</b>begin<b>(</b><b>)</b><b>;</b> i <b>!=</b> end<b>;</b> <b>++</b>i<b>)</b>
<b>{</b>
v1<b>.</b>erase<b>(</b>i<b>)</b><b>;</b>
<b>}</b>
<b>}</b>
</pre>
<P>
What's wrong with the assumptions in this code? How can this be made
more efficient?
</P>
<P>
<B>Answer #6:</B>
</P>
<P>
First and foremost, <TT>i</TT> is an iterator into a vector <TT>v</TT>, but after you
erase an element the iterator becomes invalid.
</p>
<P>
When a vector removes an element, it moves everything around, and the
old iterators are pointing to the wrong things, possibly to the
wrong memory entirely. Do not use invalid iterators, they are some
of the toughest bugs to track down!
</P>
<P>
We could replace the body of <TT>f()</TT> with this:
</P>
<pre>
<b>void</b> f<b>(</b><b>)</b>
<b>{</b>
<b>int</b> size <b>=</b> v1<b>.</b>size<b>(</b><b>)</b><b>;</b>
<b>for</b> <b>(</b><b>int</b> ii <b>=</b> <font color="blue">0</font><b>;</b> ii < size<b>;</b> <b>++</b>ii<b>)</b>
<b>{</b>
v1<b>.</b>erase<b>(</b>v1<b>.</b>begin<b>(</b><b>)</b><b>)</b><b>;</b>
<b>}</b>
<b>}</b>
</pre>
<P>
This is now correct, but is extremely slow. The vector must shift
all elements after the removed element to fill in the "hole".
Therefore, it must shift <TT>N</TT> elements <TT>N</TT> times, making this
an <TT>N^2</TT> algorithm.
</P>
<P>
Can we do better? Yes.
</P>
<P>
One might try a more efficient algorithm: instead of removing the head
of the vector, remove the tail. Then nothing needs shifting because
its only the tail that is removed, thus no holes are introduced into
the vector.
</p>
<pre>
<b>void</b> f<b>(</b><b>)</b>
<b>{</b>
<b>int</b> size <b>=</b> v1<b>.</b>size<b>(</b><b>)</b><b>;</b>
<b>for</b> <b>(</b>size_t ii <b>=</b> <font color="blue">0</font><b>;</b> ii < size<b>;</b> <b>++</b>ii<b>)</b>
<b>{</b>
v1<b>.</b>erase<b>(</b>v1<b>.</b>end<b>(</b><b>)</b><b>)</b><b>;</b>
<b>}</b>
<b>}</b>
</pre>
<P>
Unfortunately, it is undefined to remove <TT>end()</TT> from a sequence, since
<TT>end()</TT> is not referring to an actual element in the container. We
could do this instead:
<pre>
<b>void</b> f<b>(</b><b>)</b>
<b>{</b>
size_t size <b>=</b> v1<b>.</b>size<b>(</b><b>)</b><b>;</b>
<b>for</b> <b>(</b>size_t ii <b>=</b> <font color="blue">0</font><b>;</b> ii < size<b>;</b> <b>++</b>ii<b>)</b>
<b>{</b>
v1<b>.</b>erase<b>(</b><b>--</b>v1<b>.</b>end<b>(</b><b>)</b><b>)</b><b>;</b>
<b>}</b>
<b>}</b>
</pre>
<P>
It may seem strange to decrement end, but for bidirectional iterator
it's ok, since we did test that the container is not empty. However,
this still creates a temporary iterator and then modifies it for each
iteration through the loop. A more efficient way would be to call
<TT>pop_back()</TT>.
<pre>
<b>void</b> f<b>(</b><b>)</b>
<b>{</b>
size_t size <b>=</b> v1<b>.</b>size<b>(</b><b>)</b><b>;</b>
<b>for</b> <b>(</b>size_t ii <b>=</b> <font color="blue">0</font><b>;</b> ii < size<b>;</b> <b>++</b>ii<b>)</b>
<b>{</b>
v1<b>.</b>pop_back<b>(</b><b>)</b><b>;</b>
<b>}</b>
<b>}</b>
</pre>
<P>
The above algorithm operates in linear time. Again I ask, is there a
better way? Again the answer is "yes".
</p>
<P>
The solution is to use the right operation. All containers have a
<TT>clear()</TT> method that efficiently removes all elements. <TT>clear()</TT> must
also be linear, but the constant overhead is certainly smaller than
for the code in the above implementation of <TT>f()</TT>.
</P>
<P>
In situations like this, there is absolutely zero benefit to rolling
your own code. Let the library do the work, it knows the best way to
operate on itself.
</P>
<pre>
<b>void</b> f<b>(</b><b>)</b>
<b>{</b>
v1<b>.</b>clear<b>(</b><b>)</b><b>;</b>
<b>}</b>
</pre>
<P>
Now the question of the century: Why does <TT>f()</TT> exist at all, if
clearing <TT>v1</TT> is all we want to accomplish? If there are no other
preconditions, and no other post conditions, calling <TT>f()</TT> has a price
of an extra function call, without ANY benefits of being an extra
function. It requires more documentation, more testing, and adds
complexity. Without other benefits, in this case, it is better to
just call <TT>clear()</TT> directly.
</P>
<P>
<B>Question #7:</B>
</P>
<pre>
<font color="navy">// remove even-valued entries from the list</font>
<b>typedef</b> std<b>:</b><b>:</b>list<A<b>*</b><b>></b> List<b>;</b>
<b>void</b> f<b>(</b>List <b>&</b> lst<b>)</b>
<b>{</b>
<font color="navy">// now operate on the list</font>
List<b>:</b><b>:</b>iterator i <b>=</b> lst<b>.</b>begin<b>(</b><b>)</b><b>;</b>
<b>while</b> <b>(</b>i <b>!=</b> lst<b>.</b>end<b>(</b><b>)</b><b>)</b>
<b>{</b>
<b>if</b> <b>(</b><b>(</b><b>*</b>i<b>)</b><b>-></b>is_even<b>(</b><b>)</b><b>)</b>
<b>{</b>
lst<b>.</b>erase<b>(</b>i<b>)</b><b>;</b>
<b>}</b>
<b>++</b>i<b>;</b>
<b>}</b>
<b>}</b>
</pre>
<P>
What common mistake is the programmer making?
</P>
<P>
<B>Answer #7:</B>
</P>
<P>
Again we have iterator invalidation. After <TT>i</TT> is erased from the list,
it becomes invalid. But later, that invalid iterator is incremented,
resulting in undefined behavior.
</P>
<P>
But this item highlights a special case: that the <TT>std::list</TT> collection
guarantees that insertions and removals of elements only affect the
specific element being removed or added, and do NOT affect other
elements, nor do they invalidate iterators referring to other elements.
</P>
<P>
With that in mind, we can rewrite the above loop to be correct.
</P>
<pre>
<font color="navy">//</font>
<font color="navy">// Correct implementation</font>
<font color="navy">//</font>
<b>void</b> f<b>(</b>List <b>&</b> lst<b>)</b>
<b>{</b>
<font color="navy">// now operate on the list</font>
List<b>:</b><b>:</b>iterator i <b>=</b> lst<b>.</b>begin<b>(</b><b>)</b><b>;</b>
<b>while</b> <b>(</b>i <b>!=</b> lst<b>.</b>end<b>(</b><b>)</b><b>)</b>
<b>{</b>
<b>if</b> <b>(</b><b>(</b><b>*</b>i<b>)</b><b>-></b>is_even<b>(</b><b>)</b><b>)</b>
<b>{</b>
lst<b>.</b>erase<b>(</b>i<b>++</b><b>)</b><b>;</b>
<b>}</b>
<b>else</b>
<b>{</b>
<b>++</b>i<b>;</b>
<b>}</b>
<b>}</b>
<b>}</b>
</pre>
<P>
The "trick" (if you consider it one) is to postfix-increment the
iterator in the call to <TT>erase()</TT>, or prefix increment it otherwise.
Why does this work? Remember how postfix increment works: It
creates a temporary copy of the iterator, increments the real
iterator, and returns the temporary. Since it is done in that exact
order, the temporary is a valid iterator when we increment. The
real iterator is safely advanced to the next node, and then the
temporary is returned to the <TT>erase()</TT> function, which then removes
that element that our iterator USED to point to. Since lists only
invalidate iterators that point to the element when it's deleted,
the real iterator is already on the next node before the old element
<TT>i</TT> referred to is removed.
</P>
<P>
Also, you will notice that in the correct implementation above, the
increment of <TT>i</TT> is moved into an <TT>else</TT> block. This way, if <TT>*i</TT> is
even, we don't increment twice.
</P>
<pre>
<font color="navy">//</font>
<font color="navy">// incorrect implementation</font>
<font color="navy">//</font>
<b>void</b> f<b>(</b>List <b>&</b> lst<b>)</b>
<b>{</b>
<font color="navy">// now operate on the list</font>
List<b>:</b><b>:</b>iterator i <b>=</b> lst<b>.</b>begin<b>(</b><b>)</b><b>;</b>
<b>while</b> <b>(</b>i <b>!=</b> lst<b>.</b>end<b>(</b><b>)</b><b>)</b>
<b>{</b>
<b>if</b> <b>(</b><b>(</b><b>*</b>i<b>)</b><b>-></b>is_even<b>(</b><b>)</b><b>)</b>
<b>{</b>
lst<b>.</b>erase<b>(</b>i<b>++</b><b>)</b><b>;</b>
<b>}</b>
<b>++</b>i<b>;</b> <font color="navy">// PROBLEM: i may be twice incremented</font>
<b>}</b>
<b>}</b>
</pre>
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?