📄 chap07.htm
字号:
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>typedef</font> Container::iterator Iter;</PRE></FONT></BLOCKQUOTE><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">uses
that alias to create another one, for <B>vector<Shape*>::iterator</B>.
Notice that the <B>container</B> type name must be used to produce the
appropriate iterator, which is defined as a nested class. Although there are
different types of iterators (forward, bidirectional, reverse, etc., which will
be explained later) they all have the same basic interface: you can increment
them with <B>++</B>, you can dereference them to produce the object
they’re currently selecting, and you can test them to see if they’re
at the end of the sequence. That’s what you’ll want to do 90% of the
time. And that’s what is done in the above example: after creating a
container, it’s filled with different types of <B>Shape*</B>. Notice that
the upcast happens as the <B>Circle</B>, <B>Square</B> or <B>Rectangle</B>
pointer is added to the <B>shapes</B> container, which doesn’t know about
those specific types but instead holds only <B>Shape*</B>. So as soon as the
pointer is added to the container it loses its specific identity and becomes an
anonymous <B>Shape*</B>. This is exactly what we want: toss them all in and let
polymorphism sort it out.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The first <B>for</B> loop creates an
iterator and sets it to the beginning of the sequence by calling the
<B>begin( )</B> member function for the container. All containers have
<B>begin( )</B> and <B>end( )</B> member functions that produce an
iterator selecting, respectively, the beginning of the sequence and one past the
end of the sequence. To test to see if you’re done, you make sure
you’re <B>!=</B> to the iterator produced by <B>end( )</B>. Not
<B><</B> or <B><=</B>. The only test that works is <B>!=</B>. So
it’s very common to write a loop like:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>for</font>(Iter i = shapes.begin(); i != shapes.end(); i++)</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This says: “take me through every
element in the sequence.”</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">What do you do with the iterator to
produce the element it’s selecting? You dereference it using (what else)
the ‘<B>*</B>’ (which is actually an overloaded operator). What you
get back is whatever the container is holding. This container holds
<B>Shape*</B>, so that’s what <B>*i</B> produces. If you want to send a
message to the <B>Shape</B>, you must select that message with <B>-></B>, so
you write the line:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>(*i)->draw();</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This calls the <B>draw( )</B>
function for the <B>Shape*</B> the iterator is currently selecting. The
parentheses are ugly but necessary to produce the proper order of evaluation. As
an alternative, <B>operator-></B> is defined so that you can
say:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>i->draw();</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">As they are destroyed or in other cases
where the pointers are removed, the STL containers <I>do not</I> call
<B>delete</B> for the pointers they contain. If you create an object on the heap
with <B>new</B> and place its pointer in a container, the container can’t
tell if that pointer is also placed inside another container. So the STL just
doesn’t do anything about it, and puts the responsibility squarely in your
lap. The last lines in the program move through and delete every object in the
container so proper cleanup occurs.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">It’s very interesting to note that
you can change the type of container that this program uses with two lines.
Instead of including <B><vector></B>, you include <B><list></B>, and
in the first <B>typedef</B> you say:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>typedef</font> std::list<Shape*> Container;</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">instead of using a <B>vector</B>.
Everything else goes untouched. This is possible not because of an interface
enforced by inheritance (there isn’t any inheritance in the STL, which
comes as a surprise when you first see it), but because the interface is
enforced by a convention adopted by the designers of the STL, precisely so you
could perform this kind of interchange. Now you can easily switch between
<B>vector</B> and <B>list</B> and see which one works fastest for your
needs.</FONT><A NAME="_Toc519042014"></A><BR></P></DIV>
<A NAME="Heading193"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
Containers of strings</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In the prior example, at the end of
<B>main( )</B>, it was necessary to move through the whole list and
<B>delete</B> all the <B>Shape</B> pointers. </FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>for</font>(Iter j = shapes.begin();
j != shapes.end(); j++)
<font color=#0000ff>delete</font> *j;</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This highlights what could be seen as a
flaw in the STL: there’s no facility in any of the STL containers to
automatically <B>delete</B> the pointers they contain, so you must do it by
hand. It’s as if the assumption of the STL designers was that containers
of pointers weren’t an interesting problem, although I assert that it is
one of the more common things you’ll want to do.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Automatically deleting a pointer turns
out to be a rather aggressive thing to do because of the <I>multiple
membership</I> problem. If a container holds a pointer to an object, it’s
not unlikely that pointer could also be in another container. A pointer to an
<B>Aluminum</B> object in a list of <B>Trash</B> pointers could also reside in a
list of <B>Aluminum</B> pointers. If that happens, which list is responsible for
cleaning up that object – that is, which list “owns” the
object?</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This question is virtually eliminated if
the object rather than a pointer resides in the list. Then it seems clear that
when the list is destroyed, the objects it contains must also be destroyed.
Here, the STL shines, as you can see when creating a container of <B>string</B>
objects. The following example stores each incoming line as a <B>string</B> in a
<B>vector<string></B>:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:StringVector.cpp</font>
<font color=#009900>// A vector of strings</font>
<font color=#009900>//{L} ../TestSuite/Test</font>
#include <font color=#004488>"..</font><font color=#004488>/require.h"</font>
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
<font color=#0000ff>int</font> main(<font color=#0000ff>int</font> argc, <font color=#0000ff>char</font>* argv[]) {
<font color=#0000ff>char</font>* fname = <font color=#004488>"StringVector.cpp"</font>;
<font color=#0000ff>if</font>(argc > 1) fname = argv[1];
ifstream in(fname);
assure(in, fname);
vector<string> strings;
string line;
<font color=#0000ff>while</font>(getline(in, line))
strings.push_back(line);
<font color=#009900>// Do something to the strings...</font>
<font color=#0000ff>int</font> i = 1;
vector<string>::iterator w;
<font color=#0000ff>for</font>(w = strings.begin();
w != strings.end(); w++) {
ostringstream ss;
ss << i++;
*w = ss.str() + <font color=#004488>": "</font> + *w;
}
<font color=#009900>// Now send them out:</font>
copy(strings.begin(), strings.end(),
ostream_iterator<string>(cout, <font color=#004488>"\n"</font>));
<font color=#009900>// Since they aren't pointers, string </font>
<font color=#009900>// objects clean themselves up! </font>
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Once the <B>vector<string>
</B>called <B>strings</B> is created, each line in the file is read into a
<B>string</B> and put in the <B>vector</B>:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE> <font color=#0000ff>while</font>(getline(in, line))
strings.push_back(line);</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The operation that’s being
performed on this file is to add line numbers. A <B>stringstream</B> provides
easy conversion from an <B>int</B> to a <B>string</B> of characters representing
that <B>int</B>.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Assembling <B>string</B> objects is quite
easy, since <B>operator+</B> is overloaded. Sensibly enough, the iterator
<B>w</B> can be dereferenced to produce a string that can be used as both an
rvalue <I>and</I> an lvalue:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>*w = ss.str() + <font color=#004488>": "</font> + *w;</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The fact that you can assign back into
the container via the iterator may seem a bit surprising at first, but
it’s a tribute to the careful design of the STL.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because the <B>vector<string></B>
contains the objects themselves, a number of interesting things take place.
First, no cleanup is necessary. Even if you were to put addresses of the
<B>string</B> objects as pointers into <I>other</I> containers, it’s clear
that <B>strings</B> is the “master list” and maintains ownership of
the objects.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Second, you are effectively using dynamic
object creation, and yet you never use <B>new</B> or <B>delete</B>! That’s
because, somehow, it’s all taken care of for you by the <B>vector</B>
(this is non-trivial. You can try to figure it out by looking at the header
files for the STL – all the code is there – but it’s quite an
exercise). Thus your coding is significantly cleaned up.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The limitation of holding objects instead
of pointers inside containers is quite severe: you can’t upcast from
derived types, thus you can’t use polymorphism. The problem with upcasting
objects by value is that they get sliced and converted until their type is
completely changed into the base type, and there’s no remnant of the
derived type left. It’s pretty safe to say that you <I>never</I> want to
do this.</FONT><A NAME="_Toc519042015"></A><BR></P></DIV>
<A NAME="Heading194"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
Inheriting from STL containers</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The power of instantly creating a
sequence of elements is amazing, and it makes you realize how much time
you’ve spent (or rather, wasted) in the past solving this particular
problem. For example, many utility programs involve reading a file into memory,
modifying the file and writing it back out to disk. One might as well take the
functionality in <B>StringVector.cpp</B> and package it into a class for later
reuse.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Now the question is: do you create a
member object of type <B>vector</B>, or do you inherit? A general guideline is
to always prefer composition (member objects) over inheritance, but with the STL
this is often not true, because there are so many existing algorithms that work
with the STL types that you may want your new type to <I>be</I> an STL type. So
the list of <B>string</B>s<B> </B>should also <I>be</I> a <B>vector</B>, thus
inheritance is desired.</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:FileEditor.h</font>
<font color=#009900>// File editor tool</font>
#ifndef FILEEDITOR_H
#define FILEEDITOR_H
#include <string>
#include <vector>
#include <iostream>
<font color=#0000ff>class</font> FileEditor :
<font color=#0000ff>public</font> std::vector<std::string> {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> open(<font color=#0000ff>char</font>* filename);
FileEditor(<font color=#0000ff>char</font>* filename) {
open(filename);
}
FileEditor() {};
<font color=#0000ff>void</font> write(std::ostream& out = std::cout);
};
#endif <font color=#009900>// FILEEDITOR_H ///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Note the careful avoidance of a global
<B>using namespace std</B> statement here, to prevent the opening of the
<B>std</B> namespace to every file that includes this header.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The constructor opens the file and reads
it into the <B>FileEditor</B>, and <B>write( )</B> puts the <B>vector</B>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -