Шрифт:
Интервал:
Закладка:
1. Включим все необходимые заголовочные файлы и заполним вектор тысячей значений 123, чтобы нам было с чем работать:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
const size_t container_size {1000};
vector<int> v (container_size, 123);
2. Теперь обратимся к элементу, лежащему за пределами вектора, с помощью оператора []:
cout << "Out of range element value: "
<< v[container_size + 10] << 'n';
3. Далее обратимся к элементу, лежащему за пределами вектора, с помощью функции at:
cout << "Out of range element value: "
<< v.at(container_size + 10) << 'n';
}
4. Запустим программу и посмотрим, что произойдет. Сообщение об ошибке характерно для GCC. Прочие компиляторы отправят другие, но аналогичные сообщения. Первая операция чтения будет выполнена успешно, но странным образом. Она не заставит программу дать сбой, но мы получим значение, отличное от 123. Мы не можем увидеть результат второго обращения, поскольку оно намеренно сгенерировало сбой. Второй подход позволяет гораздо раньше выявить случайные выходы за границы контейнера.
Out of range element value: -726629391
terminate called after throwing an instance of 'std::out_of_range'
what(): array::at: n (which is 1010) >= _Nm (which is 1000)
Aborted (core dumped)
Как это работает
Контейнер std::vector предоставляет оператор [] и функцию at, и они, по сути, делают одинаковую работу. Однако функция выполняет дополнительные проверки границ и генерирует исключение, если мы вышли за границы вектора. Такое свойство очень полезно в ситуациях вроде нашей, но работа программы при этом несколько замедляется.
Оператор [] полезен при выполнении вычислений, для которых нужно очень быстро обращаться к проиндексированным элементам вектора. В любой другой ситуации функция at помогает определять ошибки, при этом вы почти не теряете в производительности.
Широко практикуется использование функции at по умолчанию. Если полученный код слишком медленный, но при этом безошибочный, то вместо данной функции можно задействовать оператор [] в тех местах, где важна высокая производительность.
Дополнительная информация
Конечно, можно обработать ситуацию выполнения доступа к элементу, лежащему за пределами вектора, вместо того чтобы прерывать работу программы. Для ее обработки нужно перехватить исключение, которое в нашем случае будет сгенерировано функцией at. Сделать это нетрудно. Мы окружим вызов функции at блоком try и определим код обработки ошибки в блоке catch:
try {
std::cout << "Out of range element value: "
<< v.at(container_size + 10) << 'n';
} catch (const std::out_of_range &e) {
std::cout << "Ooops, out of range access detected: "
<< e.what() << 'n';
}
Кстати говоря, контейнер std::array также предоставляет функцию at.
Сохраняем сортировку экземпляров класса std::vector
Массивы и векторы не сортируются самостоятельно. Если нам потребуется такая возможность, мы всегда можем воспользоваться структурами данных, которые предоставляют ее автоматически. Контейнер std::vector идеально подходит для нашего случая, ведь добавлять в него новые элементы в порядке сортировки несложно и удобно.
Как это делается
В этом примере мы заполним контейнер std::vector случайными словами, отсортируем их, а затем вставим дополнительные слова с учетом сортировки.
1. Сначала включим все необходимые заголовочные файлы:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
#include <cassert>
2. Кроме того, объявим пространство имен std, чтобы не писать префиксы std:::
using namespace std;
3. Далее напишем небольшую функцию main, в которой вектор заполняется случайными строками:
int main()
{
vector<string> v {"some", "random", "words",
"without", "order", "aaa",
"yyy"};
4. Затем отсортируем вектор. Для этого воспользуемся некоторыми утверждениями и функцией is_sorted из STL, показывающей, что изначально вектор не был отсортирован, а теперь все его элементы расположены по порядку:
assert(false == is_sorted(begin(v), end(v)));
sort(begin(v), end(v));
assert(true == is_sorted(begin(v), end(v)));
5. Наконец, добавим случайные слова в отсортированный вектор с помощью новой функции insert_sorted, которую будем реализовывать далее. Эти слова сразу нужно помещать в правильную позицию, поэтому вектор останется отсортированным:
insert_sorted(v, "foobar");
insert_sorted(v, "zzz");
6. Теперь реализуем функцию insert_sorted и расположим ее перед функцией main:
void insert_sorted(vector<string> &v, const string &word)
{
const auto insert_pos (lower_bound(begin(v), end(v), word));
v.insert(insert_pos, word);
}
7. Теперь вернемся в функцию main — туда, где мы остановились, — и продолжим работу, выведя содержимое вектора и увидев, что процедура вставки отработала:
for (const auto &w : v) {
cout << w << " ";
}
cout << 'n';
}
8. Компиляция и запуск программы дадут следующий результат:
aaa foobar order random some without words yyy zzz
Как это работает
Вся программа построена вокруг функции insert_sorted, решающей задачу, которой посвящен этот раздел. Для каждой новой строки эта функция определяет позицию в отсортированном векторе (куда нужно произвести вставку), позволяющую сохранить порядок строк в векторе. Однако мы предполагаем, что вектор был отсортирован заранее. Иначе этот подход не сработает.
Позиция определяется с помощью функции STL lower_bound, принимающей три аргумента. Первые два из них указывают на начало и конец диапазона. В нашем случае таковым является вектор слов. Третий аргумент — вставляемое слово. Функция находит первый элемент диапазона, который больше ее третьего параметра или равен ему, и возвращает итератор, указывающий на него.
Определив правильную позицию, мы передаем ее методу insert контейнера std::vector, который принимает всего два аргумента. Первый аргумент — итератор, указывающий на позицию в векторе, в которую будет вставлен второй параметр. Очень удобно, что можно использовать итератор, возвращаемый функцией lower_ bound. Второй аргумент — это, конечно же, вставляемый элемент.
Дополнительная информация
Функция insert_sorted довольно универсальна. Если