Урок 5. Массивы и std::vector
Чтобы прочитать тысячу чисел и потом вывести их в обратном порядке, не заводят тысячу переменных. Берут массив — ряд пронумерованных ячеек одного типа под одним именем.
Обычный массив
#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 50int a[5]— массив из пяти целых чисел. Размер пишется в квадратных скобках и потом не меняется.- Ячейки (элементы) нумеруются с нуля: у массива из пяти элементов номера (индексы) от 0 до 4.
a[i]— элемент с индексомi; его можно читать и менять, как обычную переменную.
Массив без начальных значений, объявленный внутри функции, заполнен «мусором». int a[5] = {}; заполняет его нулями. Массивы, объявленные вне всех функций (глобальные), заполняются нулями сами.
Выход за границы массива — a[5] или a[-1] у массива из пяти элементов — C++ не проверяет. Программа прочитает или испортит чужую память: выдаст неверный ответ или упадёт с ошибкой segmentation fault. Что делать в таком случае, рассказывает справка Segmentation fault.
Размер обычного массива должен быть известен при сборке: int a[n], где n прочитано с клавиатуры, в стандартном C++ писать нельзя. g++ такое собирает как своё расширение языка, а Visual Studio выдаёт ошибку. Для массива, размер которого узнаётся при работе программы, есть vector.
std::vector
vector — массив, который сам следит за своим размером и умеет расти. Для него нужен #include <vector>. В угловых скобках пишется тип элементов:
#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 55 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 пустых строк |
Добавление и удаление
#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| Действие | Что делает |
|---|---|
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, а не испортит память. Удобно при поиске ошибок.
Цикл по всем элементам
Чтобы пройти по всем элементам, не нужен счётчик:
#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 10for (int x : v) читается как «для каждого x из v». В x попадает копия элемента, поэтому менять элементы так нельзя. for (int& x : v) с & даёт сам элемент, и изменения остаются в векторе. Что такое &, подробно — в уроке Функции и ссылки.
Ловушка с size()
v.size() возвращает беззнаковое число (оно не бывает отрицательным). Из-за этого две ловушки:
for (int i = 0; i < v.size(); i++)с ключом-Wallдаёт предупреждениеcomparison of integer expressions of different signedness. Пишитеi < (int)v.size()или цикл по элементам.v.size() - 1у пустого вектора — не −1, а огромное число (в 64-битной программе около 1,8·10¹⁹). Циклfor (int i = 0; i < v.size() - 1; i++)на пустом векторе уйдёт далеко за границы.
#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 столбцов — вектор векторов:
#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 66
15t[i][j] — элемент в строке i и столбце j. Обычный двумерный массив пишется так же: int t[100][100];.
Коротко
- Индексы начинаются с 0: у массива из
nэлементов последний —a[n - 1]. - Выход за границы C++ не ловит: неверный ответ или падение программы.
vector<int> v(n);— массив изnнулей, размер которого задаётся при работе программы.push_back— добавить в конец,size()— размер,back()— последний элемент.for (int x : v)— копии элементов,for (int& x : v)— сами элементы.size()беззнаковый: кладите его вintперед сравнениями.