Объявление массива
Объявляет объект типа массив.
Содержание |
[править] Синтакс
Объявление массива это любое простое объявление, у которого декларатор имеет форму
декларатор-не-указатель [ выражение (необязательно) ] атрибуты (необязательно)
|
|||||||||
декларатор-не-указатель | — | любой допустимый декларатор, но если он начинается с * , & или && , он должен быть заключён в круглые скобки.
|
выражение | — | целочисленное константное выражение (до 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 по Объявление массива
|