На первую часть вашего вопроса уже ответили другие. Никакой формы наследования не происходит. Вектор ведет себя как вектор, и ничего больше.
Есть два способа манипулировать массивами. Первый (очевидный) - через цикл for, как вы сказали:
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;
}
Другой подход заключается в использовании алгоритмов, определенных в стандартной библиотеке. Как введение в те, я собираюсь разделить это на несколько шагов. Во-первых, каждый контейнер также определяет тип итератора. Итераторы концептуально похожи на указатели, указывающие на местоположение в контейнере. Поэтому вместо vector[i].printString()
вы можете вызывать printString () для элемента, на который указывает любой данный итератор. (при условии, что итератор с именем iter
, синтаксис будет iter->printString()
)
Причина этого заключается в том, что он позволяет использовать общий и общий способ обхода контейнеров. Поскольку списки, векторы, запросы и все другие типы контейнеров предоставляют итераторы, и эти итераторы используют один и тот же синтаксис, ваш код может использовать пару итераторов, обозначая начало и конец диапазона элементов, которые вы хотите обработать, а затем один и тот же код будет работать независимо от базового типа контейнера.
Итак, сначала давайте воспользуемся циклом, чтобы снова запустить контейнер, но на этот раз с помощью итераторов:
forstd::vector<MyClass> current = vector.begin(); current != vector.end(); ++current) {
cout << "mystring is: " << current->printString() << endl;
cout << "number is : " << current->printNumber() << endl;
}
Пока не очень большое улучшение, хотя оно устраняет индексную переменную i
, которая часто не требуется, за исключением счетчика циклов. Функции begin / end) возвращают итератор, указывающий на первый элемент в контейнере, а другой указывает один после конца итератора. Поэтому, когда мы перемещаем первый итератор вперед, мы знаем, что достигли конца, когда он равен конечному итератору. Таким образом, два итератора могут представлять любой диапазон элементов.
Теперь, когда у нас есть итераторы, мы можем использовать множество других приемов. Стандартная библиотека C ++ поставляется с рядом алгоритмов для обработки последовательностей элементов. Они расположены в заголовке <algorithm>
.
Самый простой способ начать работу - это std::for_each
, который почти заменяет цикл for. Это просто функция, которая принимает два итератора, определяя диапазон элементов, которые она должна обрабатывать, и действие, которое она должна выполнять над каждым из них. Итак, чтобы назвать это, нам нужно определить такое действие:
void Print(const MyClass& obj) {
cout << "mystring is: " << obj.printString() << endl;
cout << "number is : " << obj.printNumber() << endl;
}
Вот и все. Функция, которая принимает тип элемента в качестве параметра и выполняет все, что нужно. Теперь мы можем позвонить for_each
:
std::for_each(vector.begin(), vector.end(), Print);
Если вам нужно делать это часто, это экономит много печатания. Функция Print должна быть определена только один раз, и тогда каждый цикл for может быть заменен таким однострочником.
Еще один приятный трюк с итераторами заключается в том, что они не должны представлять весь диапазон. Мы могли бы пропустить первые пять элементов:
std::for_each(vector.begin() + 5, vector.end(), Print);
или возьмите только первые три элемента:
std::for_each(vector.begin(), vector.begin()+3, Print);
или любые другие манипуляции, о которых вы можете подумать.
Существуют также алгоритмы, такие как копирование (копирование из одного диапазона итератора в другой):
std::copy(vector.begin(), vector.end(), dest.begin());
И dest
также может быть любым типом итератора, он не обязательно должен быть векторным итератором только потому, что источником является. На самом деле мы могли бы даже напрямую скопировать в std :: cout, если вы хотите распечатать содержимое напрямую (к сожалению, поскольку MyClass не определяет operator <<
, это приведет к ошибке.)
Чтобы обойти эту маленькую проблему с std :: cout, мы могли бы использовать std::transform
, который применяет некоторые преобразования к каждому объекту, а затем помещает результат в выходную последовательность. Поскольку мы не можем напрямую распечатать объект MyClass, мы могли бы просто преобразовать его в строку, которую можно распечатать:
std::string ToString(const MyClass& obj) {
return std::string("mystring is: " + obj.printString() + "\nnumber is :" << obj.printNumber() + "\n";
}
Опять довольно простой код. Мы просто создаем функцию, которая принимает объект MyClass и создает строку с желаемым выводом. Итак, давайте скопируем это прямо в std :: cout:
std::transform(vector.begin(), vector.end(), std::ostream_iterator(std::cout), ToString);
std::ostream_iterator
создает специальный итератор потока вывода из std::cout
, чтобы позволить ему функционировать как итератор. И снова, реальный код «делай это на всем в векторе» стал одной строкой. Фактическое действие, которое нужно выполнить, определяется один раз, в другом месте, поэтому не нужно загромождать код.
Таким образом, хотя цикл for является очевидным способом обработки последовательностей элементов в контейнере, итераторы часто являются лучшим решением в долгосрочной перспективе. Они предлагают гораздо большую гибкость и даже немного упрощают ваш код.
Я не буду винить вас, если вы предпочитаете пока использовать циклы for, поскольку их немного легче прогуливать. Я просто хотел показать вам, что они не являются «окончательным» ответом в C ++.