В целом, если требуется использовать массив, вместо него следует использовать
vector
.
vector
предлагает большую безопасность и гибкость, чем массив, а накладные расходы на производительность в большинстве случаев пренебрежимо малы, и если окажется, что они больше, чем можно
себе позволить, производительность
vector
можно увеличить, использовав некоторые его методы.
Если вы не знакомы с контейнерами, поставляющимися в составе стандартной библиотеки, или не сталкивались с использованием шаблонов классов (и их написанием), то объявление
vector
в примере 6.1 требует некоторых пояснений. Объявление
vector
имеет следующий вид.
vector<typename Value, // Тип элемента, который будет храниться в этом векторе
Стандартные контейнеры параметризованы по типу объектов, которые будут в них храниться. Также есть параметр шаблона для используемого распределителя памяти, но по умолчанию он имеет стандартное значение и обычно не указывается, так что я его здесь обсуждать не буду.
Если вы хотите, чтобы
vector
хранил элементы типа
int
, объявите его, как в этом примере.
vector<int> intVec;
А если вам требуется, чтобы он хранил строки, просто измените тип аргумента
vector
.
vector<string> strVec;
vector
может содержать любой тип С++, который поддерживает конструктор копирования и присвоение.
Следующее, что логически требуется сделать после создания экземпляра
vector
, — это что-либо поместить в него. В конец вектора элементы добавляются с помощью
push_back
.
intVec.push_back(3);
intVec.push_back(9);
intVec.push_back(6);
Это примерно эквивалентно добавлению элементов 0, 1 и 2 в массив. Это «примерно» эквивалентно потому, что, конечно,
push_back
— это метод, который возвращает
void
и помещает свой аргумент в конец вектора.
operator[]
возвращает ссылку на область памяти, на которую указывает индекс массива,
push_back
гарантирует, что во внутреннем буфере
vector
окажется достаточно места для добавления аргумента. Если место есть, то он добавляется в следующий неиспользуемый индекс, а если нет, то буфер увеличивается с помощью зависящего от реализации алгоритма, а затем в него добавляется аргумент
Также с помощью метода
insert
можно вставить элементы в середину вектора, хотя этого следует избегать из-за линейно возрастающей сложности этой операции. За более подробным обсуждением проблем производительности и их решения при использовании
vector
обратитесь к рецепту 6.2. Чтобы вставить элемент, получите итератор на точку, куда требуется его вставить (обсуждение итераторов приводится в рецепте 7.1).
string s = "Marines";
vector<string>::iterator p = find(strVec.begin
strVec.end, s);
if (s != strVec.end) //
Вставляет s непосредственно перед элементом,
strVec.insert(p, s); // на который указывает p
Перегруженные версии
insert
позволяют вставлять в вектор n копий объекта, а также вставлять целый диапазон другой последовательности (эта последовательность может быть другим
vector
, массивом,
list
и т.п.).
Вместо вставки можно просто присвоить вектору уже существующую другую последовательность, стерев при этом то, что в нем содержалось до этого. Это выполняет метод
assign
. Вектору можно присвоить диапазон значений или n копий одного и того же объекта, как здесь.
string sarr[3] = {"Ernie", "Bert", "Elmo"};
string s = "Oscar";
strVec.assign(&sarr[0], &sarr[3]); // Присвоить эту последовательность
strVec.assign(50, s); // Присвоить 50 копий s
Если новая последовательность окажется больше, чем имеющийся размер буфера
vector
, то
assign
изменит размер буфера так, чтобы разместить в нем всю новую последовательность.
После того как данные помещены в
vector
, имеется несколько способов получения их назад. Вероятно, наиболее интуитивным является
operator[]
, который возвращает ссылку или
const
– ссылку в зависимости от того, является ли вектор
const
или нет, на элемент по указанному индексу. В этом отношении он ведет себя почти как массив:
for (int i = 0; i < intVec.size; ++i) {
std::cout << "intVec[" << i << "] = "
<< intVec[i] << '\n'; // rvalue
}
intVec[2] = 32; // lvalue
operator[]
также ведет себя как массив в том, что при использовании индекса, который больше, чем индекс последнего элемента
vector
, результат не определен, что обычно означает, что будут повреждены данные программы или она обрушится. Избежать этого можно, запросив число элементов, содержащихся в
vector
, с помощью
size
. Однако использованию
operator[]
следует предпочитать итераторы, так как их использование является стандартным для перебора элементов любого стандартного контейнера.
for (vector<string>::iterator p = strVec.begin;
p != strVec.end; ++p) {
std::cout << *p << '\n';
}
Итераторы являются наиболее мощным подходом, так как они позволяют обращаться с контейнерами одинаковым образом. Например, при написании алгоритма, который работает с последовательностями элементов, расположенными между двумя итераторами, он сможет работать с любым стандартным контейнером. Это общий подход. При использовании произвольного доступа с помощью
operator[]
вы ограничиваете себя использованием только тех контейнеров, которые поддерживают произвольный доступ. Первый подход позволяет алгоритмам стандартной библиотеки из
<algorithm>
одинаково работать со стандартными контейнерами (и другими типами, ведущими себя, как они).