Глиф Учебники

Урок 5. Массивы и std::vector

Курс «C++» · урок 5 из 10 · 3 минуты чтения

Чтобы прочитать тысячу чисел и потом вывести их в обратном порядке, не заводят тысячу переменных. Берут массив — ряд пронумерованных ячеек одного типа под одним именем.

Обычный массив

C++
#include <iostream>
using namespace std;

int main() {
    int a[5] = {10, 20, 30, 40, 50};
    cout << a[0] << " " << a[4] << "\n";
    a[2] = 99;
    for (int i = 0; i < 5; i++) {
        cout << a[i] << " ";
    }
    cout << "\n";
    return 0;
}
Вывод
10 50
10 20 99 40 50

Массив без начальных значений, объявленный внутри функции, заполнен «мусором». int a[5] = {}; заполняет его нулями. Массивы, объявленные вне всех функций (глобальные), заполняются нулями сами.

Выход за границы массива — a[5] или a[-1] у массива из пяти элементов — C++ не проверяет. Программа прочитает или испортит чужую память: выдаст неверный ответ или упадёт с ошибкой segmentation fault. Что делать в таком случае, рассказывает справка Segmentation fault.

int a[5] = {10, 20, 30, 40, 50}; a[0] 10 a[1] 20 a[2] 30 a[3] 40 a[4] 50 a[-1] ? чужая память a[5] ? чужая память 5 элементов — индексы от 0 до 4
У массива из пяти элементов индексы идут от 0 до 4; ячейки с индексами −1 и 5 лежат за границами массива, в чужой памяти, и C++ такое обращение не проверяет.

Размер обычного массива должен быть известен при сборке: int a[n], где n прочитано с клавиатуры, в стандартном C++ писать нельзя. g++ такое собирает как своё расширение языка, а Visual Studio выдаёт ошибку. Для массива, размер которого узнаётся при работе программы, есть vector.

std::vector

vector — массив, который сам следит за своим размером и умеет расти. Для него нужен #include <vector>. В угловых скобках пишется тип элементов:

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> a(n);          // n элементов, все равны 0
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for (int i = n - 1; i >= 0; i--) {
        cout << a[i] << " ";
    }
    cout << "\n";
    return 0;
}
Ввод
5
1 2 3 4 5
Вывод
5 4 3 2 1

Способы создать вектор:

ЗаписьЧто получится
vector<int> v;пустой вектор
vector<int> v(n);n нулей
vector<int> v(n, 7);n семёрок
vector<int> v = {3, 1, 4};три элемента: 3, 1, 4
vector<string> names(10);10 пустых строк

Добавление и удаление

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v;
    v.push_back(3);
    v.push_back(1);
    v.push_back(4);
    cout << v.size() << " " << v.front() << " " << v.back() << "\n";
    v.pop_back();
    cout << v.size() << " " << v.back() << "\n";
    v.clear();
    cout << v.empty() << "\n";
    return 0;
}
Вывод
3 3 4
2 1
1
vector<int> v; пусто size() = 0 v.push_back(3); 3 size() = 1 v.push_back(1); 3 1 size() = 2 v.push_back(4); 3 1 4 size() = 3 front() = 3, back() = 4 v.pop_back(); 3 1 4 size() = 2, back() = 1 v.clear(); 3 1 empty() — true
push_back добавляет элемент в конец и увеличивает size(), pop_back убирает последний элемент, а clear удаляет все — после него empty() возвращает true.
ДействиеЧто делает
v.push_back(x)добавить x в конец
v.pop_back()удалить последний элемент
v.size()количество элементов
v.front(), v.back()первый и последний элемент
v.empty()true, если вектор пуст
v.clear()удалить все элементы

push_back удобен, когда количество заранее неизвестно: значения читают и добавляют, пока есть данные.

v[i], как и у массива, границы не проверяет. v.at(i) проверяет: при неверном индексе программа остановится с понятной ошибкой out_of_range, а не испортит память. Удобно при поиске ошибок.

Цикл по всем элементам

Чтобы пройти по всем элементам, не нужен счётчик:

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {5, 2, 8, 1};
    int best = v[0];
    for (int x : v) {
        if (x > best) {
            best = x;
        }
    }
    cout << "max " << best << "\n";
    for (int& x : v) {
        x *= 10;
    }
    for (int x : v) {
        cout << x << " ";
    }
    cout << "\n";
    return 0;
}
Вывод
max 8
50 20 80 10

for (int x : v) читается как «для каждого x из v». В x попадает копия элемента, поэтому менять элементы так нельзя. for (int& x : v) с & даёт сам элемент, и изменения остаются в векторе. Что такое &, подробно — в уроке Функции и ссылки.

Ловушка с size()

v.size() возвращает беззнаковое число (оно не бывает отрицательным). Из-за этого две ловушки:

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {4, 7, 7, 2, 7};
    int n = v.size();
    int sevens = 0;
    for (int i = 0; i < n; i++) {
        if (v[i] == 7) {
            sevens++;
        }
    }
    cout << sevens << "\n";
    return 0;
}
Вывод
3

Размер один раз кладут в переменную int n — дальше сравнения обычные, без предупреждений.

Двумерные массивы

Таблица из n строк и m столбцов — вектор векторов:

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> t(n, vector<int>(m));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> t[i][j];
        }
    }
    for (int i = 0; i < n; i++) {
        int sum = 0;
        for (int j = 0; j < m; j++) {
            sum += t[i][j];
        }
        cout << sum << "\n";
    }
    return 0;
}
Ввод
2 3
1 2 3
4 5 6
Вывод
6
15

t[i][j] — элемент в строке i и столбце j. Обычный двумерный массив пишется так же: int t[100][100];.

vector<vector<int>> t(n, vector<int>(m)); ввод: n = 2, m = 3 вектор строк каждая строка — vector<int> [0] [1] [2] t[0] 1 2 3 сумма строки 6 t[1] 4 5 6 сумма строки 15 t[1][2] строка 1, столбец 2
Двумерный вектор t — вектор из n строк, каждая строка — vector<int> из m чисел; в строке 1 и столбце 2 лежит 6, а программа выводит суммы строк 6 и 15.

Коротко

Проверьте себя

1. Какой индекс у последнего элемента массива int a[10]?
2. Что делает v.push_back(5)?
3. Почему for (int x : v) x = 0; не обнуляет вектор?
4. Что выведет программа?
C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v(3, 2);
    v.push_back(5);
    cout << v.size() << " " << v[0] + v[3] << "\n";
    return 0;
}