Introducing
vector
With
strings,
we can fill up a
string
object without knowing how much storage we’re going to need. The problem
with reading lines from a file into individual
string
objects
is that you don’t know up front how many
strings
you’re going to need – you only know after you’ve read the
entire file. To solve this problem, we need some sort of holder that will
automatically expand to contain as many
string
objects as we care to put into it.
In
fact, why limit ourselves to holding
string
objects? It turns out that this kind of problem – not knowing how many of
something you have while you’re writing a program – happens a lot.
And this “container” object sounds like it would be more useful if
it would hold
any
kind of object at all!
Fortunately, the Standard C++ library has a ready-made solution: the STL
container classes. STL stands for “Standard Template Library,” and
it’s one of the real powerhouses of Standard C++. Even though the
implementation of the STL uses some advanced concepts, and the full coverage of
the STL is given two large chapters later in this book, this library can also
be very potent without knowing a lot about it. It’s so useful that the
most basic of the STL containers, the
vector,
is introduced in this early chapter and used throughout the introductory part
of the book. You’ll find that you can do a tremendous amount just by
using the basics of
vector
and not worrying about the underlying implementation (again, an important goal
of OOP). Since you’ll learn much more about this and the other containers
when you reach the STL chapters, it seems forgiveable if the programs that use
vector
in
the early portion of the book aren’t exactly what an experienced C++
programmer would do. You’ll find that in most cases, the usage shown here
is adequate.
The
vector
class is a
template,
which means that it can be efficiently applied to different types. That is, we
can create a
vector
of
shapes,
a
vector
of
cats,
a
vector
of
strings,
etc. Basically, with a template you can create a “class of
anything.” To tell the compiler what it is that the class will work with
(in this case, what the
vector
will hold), you put the name of the desired type in “angle
brackets,” which means ‘less-than’ and
‘greater-than’ signs. So a
vector
of
string
would be denoted
vector<string>.
When you do this, you end up with a customized vector that will only hold
string
objects, and you’ll get an error message from the compiler if you try to
put anything else into it.
Since
vector
expresses
the concept of a “container,” there must be a way to put things
into the container and get things back out of the container. To add a brand-new
element on the end of a
vector,
you use a the member function
push_back( )
(remember that, since it’s a member function, you use a ‘
.’
to call it for a particular object). The reason the name of this member
function might seem a bit verbose –
push_back( )
instead of something simpler like “put” – is because there
are other containers and other member functions for putting new elements into
containers. For example, the there is an
insert( )
member function to put something in the middle of a container.
vector
supports this but its use is more complicated and we won’t need to
explore it until later in the book. There’s also a
push_front( )
(not part of
vector)
to put things at the beginning. There are many more member functions in
vector
and many more containers in the STL, but you’ll be surprised at how much
you can do just knowing about a few simple features.
So
you can put new elements into a
vector
with
push_back( ),
but how do you get these elements back out again? This solution is more clever
and elegant – operator overloading is used to make the
vector
look like an
array.
The array (which will be described more fully in the next chapter) is a data
type which is available in virtually every programming language so you should
already be somewhat familiar with it. Arrays are
aggregates,
which mean they consists of a number of elements clumped together. The
distinguishing characteristic of an array is that these elements are the same
size and are arranged to be one right after the other. Most importantly, these
elements can be selected by “indexing,” which means you can say
“I want element number n” and that element will be produced,
usually very quickly. Although there are exceptions in programming languages,
the indexing is normally achieved using square brackets, so if you have an array
a
and you want to produce element 5, you say
a[5]. This
very compact and powerful indexing notation is incorporated into the
vector
using operator overloading, just like ‘
<<’
and ‘
>>’
were incorporated into iostreams. Again, you don’t need to know how the
overloading was implemented – that’s saved for a later chapter
– but it’s helpful if you’re aware that there’s some
magic going on under the covers in order to make the
[ ]
work with
vector. With
that in mind, you can now see a program that uses
vector.
To use a
vector,
you include the header file
<vector>:
//: C02:Fillvector.cpp
// Copy an entire file into a vector of string
#include <string>
#include <fstream>
#include <vector>
using namespace std;
int main() {
vector<string> v;
ifstream in("Fillvector.cpp");
string line;
while(getline(in, line))
v.push_back(line); // Add the line to the end
// Add line numbers:
for(int i = 0; i < v.size(); i++)
cout << i << ": " << v[i] << endl;
} ///:~
Much
of this program is similar to the previous one: a file is opened and lines are
read into
string
objects one at a time. However, these
string
objects are pushed onto the back of the
vector
v
.
Once the
while
loop completes, the entire file is resident in memory, inside
v. The
next statement in the program is called a
for
loop. It is similar to a
while
loop except that it adds some extra control. After the
for,
there is a “control expression” inside of parentheses, just like the
while
loop. However, this control expression is in three parts: a part which
initializes, one that tests to see if we should exit the loop, and one which
changes something, typically to step through a sequence of items. This program
shows the
for
loop in the way you’ll see it most commonly used: the initialization part
int
i = 0
creates an integer
i
to use as a loop counter and gives it an initial value of zero. The testing
portion says that to stay in the loop,
i
should be less than the number of elements in the
vector
v
(this is produced using the member function
size( )
which I just sort of slipped in here, but you must admit it has a fairly
obvious meaning). The final portion uses a shorthand for C and C++, the
“auto-increment” operator, to add one to the value of
i.
Effectively,
i++
says “get the value of
i,
add one to it, and put the result back into
i.
Thus, the total effect of the
for
loop is to take a variable
i
and march it through the values from zero to one less than the size of the
vector.
For each value of
i,
the
cout
statement is executed and this builds a line that consists of the value of
i
(magically converted to a character array by
cout),
a colon and a space, the line from the file, and a newline provided by
endl.
When you compile and run it you’ll see the effect is to add line numbers
to the file.
Because
of the way that the ‘
>>’
operator works with iostreams, you can easily modify the above program so that
it breaks up the input into whitespace-separated words instead of lines:
//: C02:GetWords.cpp
// Break a file into whitespace-separated words
#include <string>
#include <fstream>
#include <vector>
using namespace std;
int main() {
vector<string> words;
ifstream in("Fillvector.cpp");
string word;
while(in >> word)
words.push_back(word);
for(int i = 0; i < words.size(); i++)
cout << words[i] << endl;
} ///:~
is
what gets the input one “word” at a time, and when this expression
evaluates to false it means the end of the file has been reached. Of course,
delimiting words by whitespace is quite crude, but it makes for a simple
example. Later in the book you’ll see more sophisticated examples that
let you break up input just about any way you’d like.
To
demonstrate how easy it is to use a
vector
with any type, here’s an example that creates a
vector<int>:
//: C02:Intvector.cpp
// Creating a vector that holds integers
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
for(int i = 0; i < 10; i++)
v.push_back(i);
for(int i = 0; i < v.size(); i++)
cout << v[i] << ", ";
cout << endl;
for(int i = 0; i < v.size(); i++)
v[i] = v[i] * 10; // Assignment
for(int i = 0; i < v.size(); i++)
cout << v[i] << ", ";
cout << endl;
} ///:~
To
create a
vector
that holds a different type, you just put that type in as the template argument
(the argument in angle brackets). Templates and well-design template libraries
are intended to be exactly this easy to use.
This
example goes on to demonstrate another essential feature of
vector.
In the expression
you
can see that the
vector
is not limited to only putting things in and getting things out. You also have
the ability to
assign
(and thus to change) to any element of a
vector,
also through the use of the square-brackets indexing operator. This means that
vector
is a very general-purpose, very flexible “scratchpad” for working
with collections of objects, and we will definitely make use of it in coming
chapters.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru