Объявление массива
Объявляет объект типа массив.
Синтакс
Объявление массива это любое простое объявление, у которого декларатор имеет форму
декларатор-не-указатель [ выражение (необязательно) ] атрибуты (необязательно)
|
|||||||||
| декларатор-не-указатель | — | любой допустимый декларатор, но если он начинается с *, & или &&, он должен быть заключён в круглые скобки.
|
| выражение | — | целочисленное константное выражение (до C++14)преобразованное константное выражение типа std::size_t (начиная с C++14), которое оценивается как значение больше нуля |
| атрибуты | — | (начиная с C++11) список атрибутов |
Объявление вида T a[N]; объявляет a как объект массив, состоящий из N последовательно размещённых объектов типа T. Элементы массива нумеруются 0, …, N - 1, к ним можно получить доступ с помощью оператора индексации operator [], например: a[0], …, a[N - 1].
Массивы могут быть созданы из любого фундаментального типа (за исключением void), указателя, указателей на элементы, классов, перечислений или из других массивов с известными границами (в этом случае массив называется многомерным). Другими словами, только объекты за исключением массивов с неизвестными границами могут быть элементами массивов. Массивы неполных типов также являются неполными типами.
|
Возможно ограниченный (начиная с C++20) спецификатор |
Не существует массивов ссылок или массивов функций.
Применение cv-квалификаторов к массиву (через typedef или манипуляции с типом шаблона) применяет квалификаторы к типу элемента, но любой массив, элементы которого имеют cv-квалификацию, должен иметь такую-же cv-квалификацию.
// a и b имеют одинаковую const-квалификацию типа "массив из 5 const char"
typedef const char CC;
CC a[5] = {};
typedef char CA[5];
const CA b = {};
Когда используется вместе с выражением new[], размер массива может быть нулевым; такой массив не имеет элементов:
int* p = new int[0]; // доступ к p[0] или *p неопределён
delete[] p; // очистка всё равно требуется
Присваивание
Массивы не могут быть изменены целиком: даже если они являются левосторонними значениями (например может быть взят адрес массива), они не могут присутствовать с левой стороны оператора присваивания:
int a[3] = {1, 2, 3}, b[3] = {4, 5, 6};
int (*p)[3] = &a; // ok: адрес может быть взят
a = b; // ошибка: a это массив
struct { int c[3]; } s1, s2 = {3, 4, 5};
s1 = s2; // ok: неявно определённый оператор присваивания копированием
// может присваивать элементы данных массивов
Приведение массива к указателю
Существует неявное преобразование из левосторонних и правосторонних значений типа массивов к правосторонним значением типа указатель: оно создаёт указатель на первый элемент массива. Это преобразование используется всякий раз, когда массивы появляются в контексте, где массивы не ожидаются, а ожидаются указатели:
#include <iostream>
#include <iterator>
#include <numeric>
void g(int (&a)[3])
{
std::cout << a[0] << '\n';
}
void f(int* p)
{
std::cout << *p << '\n';
}
int main()
{
int a[3] = {1, 2, 3};
int* p = a;
std::cout << sizeof a << '\n' // выводит размер массива
<< sizeof p << '\n'; // выводит размер указателя
// когда доступны массивы, но не указатели, можно использовать только массивы
g(a); // ok: функция принимает массив по ссылке
// g(p); // ошибка
for(int n: a) // ok: массивы могут быть использованы в основанных
// на диапазоне циклах for
std::cout << n << ' '; // выводит элементы массива
// for(int n: p) // ошибка
// std::cout << n << ' ';
std::iota(std::begin(a), std::end(a), 7); // ok: begin и end принимают массивы
// std::iota(std::begin(p), std::end(p), 7); // ошибка
// где указатели допустимы, а массивы нет, могут быть использованы как одни,
// так и другие:
f(a); // ok: функция принимает указатель
f(p); // ok: функция принимает указатель
std::cout << *a << '\n' // выводит первый элемент
<< *p << '\n' // так-же
<< *(a + 1) << ' ' << a[1] << '\n' // выводит второй элемент
<< *(p + 1) << ' ' << p[1] << '\n'; // так-же
}
Многомерные массивы
Когда типом элементов массива является другой массив, говорят, что массив является многомерным:
// массив из 2-ух массивов, в каждом из которых содержится 3 int
int a[2][3] = {{1, 2, 3}, // можно рассматривать как матрицу 2 × 3
{4, 5, 6}}; // с построчной компоновкой
Важно отметить, что когда применяется преобразование массива в указатель, многомерный массив преобразуется в указатель на его первый элемент (например, указатель на его первую строку или на его первую плоскость): преобразование массива в указатель применяется только один раз.
int a[2]; // массив из 2-ух int
int* p1 = a; // превращается в указатель на первый элемент массива
int b[2][3]; // массив из 2-ух массивов, в каждом из которых по 3 int
// int** p2 = b; // ошибка: b не приводится к int**
int (*p2)[3] = b; // b приводится к указателю на первую строку из 3-ёх элементов в b
int c[2][3][4]; // массив из 2-ух массивов, каждый из которых содержит 3 массива,
// содержащих 4 int
// int*** p3 = c; // ошибка: c не приводится к int***
int (*p3)[3][4] = c; // c приводится к указателю на первую 3 × 4-элементную плоскость c
Массивы с неизвестными границами
Если выражение опущено в объявлении массива, объявляется "массив с неизвестными границами T", который является разновидностью неполного типа, за исключением случаев, когда он используется в объявлении с агрегатным инициализатором:
extern int x[]; // тип x это "массив с неизвестными границами типа int"
int a[] = {1, 2, 3}; // тип a это "массив из 3-ёх int"
Поскольку элементы массива не могут быть массивами с неизвестными границами, многомерные массивы не могут иметь неизвестных границ в одном из измерений, отличном от первого:
extern int a[][2]; // ok: массив с неизвестными границами, содержащий массивы из 2-ух int
extern int b[2][]; // ошибка: массив имеет неполный тип элементов
Если существует предыдущее объявление сущности в той же области видимости, в которой была указана граница, опущенная граница массива считается такой же, как в этом более раннем объявлении, и аналогично для определения статического элемента данных класса:
extern int x[10];
struct S
{
static int y[10];
};
int x[]; // OK: граница равна 10
int S::y[]; // OK: граница равна 10
void f()
{
extern int x[];
int i = sizeof(x); // ошибка: неполный тип объекта
}
Ссылки и указатели на массивы с неизвестными границами могут быть сформированы, но не (до C++20)и могут (начиная с C++20) могут быть инициализированы или присвоены из массивов и указателей на массивы с известными границами. Нужно заметить, что в языке программирования C указатели на массивы с неизвестными границами совместимы с указателями на массивы с известными границами и, таким образом, могут быть преобразованы и присвоены в обоих направлениях.
extern int a1[];
int (&r1)[] = a1; // ok
int (*p1)[] = &a1; // ok
int (*q)[2] = &a1; // ошибка (но ok в C)
int a2[] = {1, 2, 3};
int (&r2)[] = a2; // ok (начиная с C++20)
int (*p2)[] = &a2; // ok (начиная с C++20)
Указатели на массивы с неизвестными границами не могут участвовать в арифметике указателей и не могут быть использованы в левой части оператора индексации, но могут быть разыменованы.
Массив значений rvalue
Несмотря на то, что массивы не могут быть возвращены из функций по значению и не могут быть целью преобразований, массив значений prvalue может быть сформирован с использованием псевдонима типа для создания временного массива, используя функциональное приведение, инициализированное скобками.
|
Как и значения prvalue класса, значения prvalue массива преобразуются в значения xvalue с помощью временной материализации при вычислении. |
(начиная с C++17) |
Массив значений xvalue может быть сформирован напрямую с помощью доступа к элементу массива правостороннего значения класса или используя std::move или иное преобразование или вызов функции, которая возвращает ссылку на правостороннее значение.
#include <iostream>
#include <type_traits>
#include <utility>
void f(int (&&x)[2][3])
{
std::cout << sizeof x << '\n';
}
struct X
{
int i[2][3];
} x;
template<typename T> using identity = T;
int main()
{
std::cout << sizeof X().i << '\n'; // размер массива
f(X().i); // ok: привязка к xvalue
// f(x.i); // ошибка: не может быть привязано
// к левостороннему значению
int a[2][3];
f(std::move(a)); // ok: привязка к xvalue
using arr_t = int[2][3];
f(arr_t{}); // ok: привязка к чистому
// правостороннему значению
f(identity<int[][3]>{{1, 2, 3}, {4, 5, 6}}); // ok: привязка к чистому
// правостороннему значению
}
Вывод:
24
24
24
24
24
Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| CWG 393 | C++98 | указатель или ссылка на массив с неизвестными границами не могут быть параметром функции |
могут |
| CWG 619 | C++98 | если она опущена, граница массива не может быть выведена из предыдущего объявления |
вывод разрешён |
| CWG 2099 | C++98 | граница массива статических элементов данных не может быть опущена, даже если предоставлен инициализатор |
пропуск позволен |
| CWG 2397 | C++11 | auto нельзя использовать в качестве типа элемента
|
можно |
Смотри также
Документация C по Объявление массива
|