The first part of your question has already been answered by others. No form of inheritance takes place. The vector behaves as a vector, and nothing else.
There are two ways to manipulate arrays. The first (obvious) one is through a for loop, like you said:
for(size_t i = 0; i<vector.size(); i++) { // technically, you should use size_t here, since that is the type returned by vector.size()
cout << "Element #" << << endl; // We're iterating through the elements contained in the vector, so printing "Vector #" doesn't make sense. There is only one vector
cout << "mystring is: " << vector[i].printString()<< endl; // [i] to access the i'th element contained in the vector
cout << "number is : " << vector[i].printNumber() << endl;
}
The other approach is to use the algorithms defined in the standard library. As an introduction to those, I'm going to split it up into a few steps. First, every container also defines an iterator type. Iterators are conceptually like pointers that point to a location in the container. So instead of vector[i].printString()
, you can call printString() on the element point to by any given iterator. (assuming an iterator called iter
, the syntax would be iter->printString()
)
The reason for this is that it allows a common and generic way to traverse containers. Because lists, vectors, deques and all other container types all provide iterators, and these iterators use the same syntax, your code can take a pair of iterators, denoting the beginning and the end of the range of elements you want to process, and then the same code will work regardless of the underlying container type.
So first, let's use a loop to run through the container again, but this time using iterators:
forstd::vector<MyClass> current = vector.begin(); current != vector.end(); ++current) {
cout << "mystring is: " << current->printString() << endl;
cout << "number is : " << current->printNumber() << endl;
}
Not a huge improvement so far, although it does eliminate the i
index variable, which often isn't necessary, except as a loop counter. The begin/end) functions return an iterator pointing to the first element in the container, and another pointing one past the end of the iterator. So as we move the first iterator forward, we know we've reached the end when it equals the end iterator. In this way, two iterators can represent any range of elements.
Now that we have iterators though, we can use a lot of other tricks. The C++ standard library comes with a number of algorithms for processing sequences of elements. They're located in the <algorithm>
header.
A simple one to get us started is std::for_each
, which is almost a drop-in replacement for a for loop. It is simply a function which takes two iterators, denoting the range of elements it should process, and an action it should perform on each. So to call it, we need to define such an action:
void Print(const MyClass& obj) {
cout << "mystring is: " << obj.printString() << endl;
cout << "number is : " << obj.printNumber() << endl;
}
That's all. A function which takes the element type as a parameter, and does whatever needs to be done. Now we can call for_each
:
std::for_each(vector.begin(), vector.end(), Print);
If you need to do this often, it saves a lot of typing. The Print function only has to be defined once, and then every for loop can be replaced with such a one-liner.
Another nice trick with iterators is that they don't have to represent the entire range. We could skip the first five elements:
std::for_each(vector.begin() + 5, vector.end(), Print);
or take only the first three elements:
std::for_each(vector.begin(), vector.begin()+3, Print);
or any other manipulation you can think of.
There are also algorithms such as copy (copy from one iterator range to another):
std::copy(vector.begin(), vector.end(), dest.begin());
And dest
may be any type of iterator as well, it doesn't have to be a vector iterator just because the source is. In fact we could even copy directly to std::cout if you wanted to print out the contents directly (unfortunately, since MyClass doesn't define the operator <<
, that would result in an error.)
to work around this little problem with std::cout, we could use std::transform
, which applies some transformation to each object, and then places the result into an output sequence. Since we can't directly print out a MyClass objec, we could just transform it to a string, which can be printed out:
std::string ToString(const MyClass& obj) {
return std::string("mystring is: " + obj.printString() + "\nnumber is :" << obj.printNumber() + "\n";
}
Again, fairly simple code. We simply create a function which takes a MyClass object, and builds a string with the desired output. So let's copy this directly to std::cout:
std::transform(vector.begin(), vector.end(), std::ostream_iterator(std::cout), ToString);
std::ostream_iterator
creates a special output stream iterator out of std::cout
to allow it to function as an iterator. And once again, the actual "do this on everything in the vector" code became a single line. The actual action to perform is defined once, elsewhere, so it doesn't have to clutter up the code.
So while a for loop is the immediately obvious way to process sequences of elements in a container, iterators are often a better solution in the long run. They offer a lot more flexibility, and even simplifies your code quite a bit.
I won't blame you if you prefer to stick with for loops for now, as they're a bit easier to grok. I simply wanted to show you that they're not the "ultimate" answer in C++.