Spec-Zone.ru › C++

Объявление массива

Объявляет объект типа массива.

Синтаксис

Объявлением массива является любое простое объявление, чей декларатор имеет вид

noptr-declarator [expr (необязательно)] attr (необязательно)
noptr-declarator - любой допустимый декларатор, но если он начинается с *, & или &&, его необходимо заключить в круглые скобки (иначе весь декларатор рассматривается как декларатор указателя или декларатор ссылки).
expr - целочисленное константное выражение(до C++14)преобразованное константное выражение типа std::size_t(начиная с C++14), результатом вычисления которого является значение больше нуля
attr - (начиная с C++11) список атрибутов

Объявление вида T a[N]; объявляет a как объект-массив, состоящий из N расположенных в памяти подряд объектов типа T. Элементы массива нумеруются от 0 до N - 1 и могут быть доступны с помощью оператора индексирования [], например a[0], …, a[N - 1].

Массивы могут быть построены из любого фундаментального типа (кроме void), указателей, указателей на члены, классов, перечислений или других массивов с известной границей (в этом случае массив называется многомерным). Иными словами, элементами типов массивов могут быть только типы объектов, кроме типов массивов с неизвестной границей. Типы массивов с неполным типом элемента также являются неполными типами.

Спецификатор auto, который может быть ограниченным(начиная с C++20), можно использовать как тип элемента массива при объявлении указателя или ссылки на массив; в этом случае тип элемента выводится из инициализатора или аргумента функции(начиная с C++14). Например, auto (*p)[42] = &a; является допустимым выражением, если a — lvalue типа int[42].

(начиная с C++11)

Массивов ссылок и массивов функций не существует.

Применение cv-квалификаторов к типу массива (через typedef или манипуляции с типами шаблонов) распространяет квалификаторы на тип элемента, но любой тип массива, элементы которого имеют cv-квалифицированный тип, считается имеющим те же cv-квалификаторы.

// a and b have the same const-qualified type "array of 5 const char"

typedef const char CC;
CC a[5] = {};

typedef char CA[5];
const CA b = {};

При использовании с выражением new[] размер массива может быть равен нулю; такой массив не содержит элементов:

int* p = new int[0]; // accessing p[0] or *p is undefined
delete[] p; // cleanup still required

Присваивание

Объекты типа массива нельзя изменять целиком: хотя они являются lvalue (например, можно получить адрес массива), они не могут находиться в левой части оператора присваивания:

int a[3] = {1, 2, 3}, b[3] = {4, 5, 6};
int (*p)[3] = &a; // okay: address of a can be taken
a = b;            // error: a is an array

struct { int c[3]; } s1, s2 = {3, 4, 5};
s1 = s2; // okay: implicitly-defined copy assignment operator
         // can assign data members of array type

Преобразование массива в указатель

Существует неявное преобразование lvalue и rvalue типа массива в rvalue типа указателя: оно создает указатель на первый элемент массива. Это преобразование применяется, когда массивы используются в контексте, где ожидаются не массивы, а указатели:

#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'  // prints size of array
              << sizeof p << '\n'; // prints size of a pointer

    // where arrays are acceptable, but pointers aren't, only arrays may be used
    g(a); // okay: function takes an array by reference
//  g(p); // error

    for (int n : a)            // okay: arrays can be used in range-for loops
        std::cout << n << ' '; // prints elements of the array
//  for (int n : p)            // error
//      std::cout << n << ' ';

    std::iota(std::begin(a), std::end(a), 7); // okay: begin and end take arrays
//  std::iota(std::begin(p), std::end(p), 7); // error

    // where pointers are acceptable, but arrays aren't, both may be used:
    f(a); // okay: function takes a pointer
    f(p); // okay: function takes a pointer

    std::cout << *a << '\n' // prints the first element
              << *p << '\n' // same
              << *(a + 1) << ' ' << a[1] << '\n'  // prints the second element
              << *(p + 1) << ' ' << p[1] << '\n'; // same
}

Многомерные массивы

Если типом элемента массива является другой массив, такой массив называется многомерным:

// array of 2 arrays of 3 int each
int a[2][3] = {{1, 2, 3},  // can be viewed as a 2 × 3 matrix
               {4, 5, 6}}; // with row-major layout

Обратите внимание: при преобразовании массива в указатель многомерный массив преобразуется в указатель на его первый элемент (например, указатель на первую строку или первую плоскость); преобразование массива в указатель применяется только один раз.

int a[2];            // array of 2 int
int* p1 = a;         // a decays to a pointer to the first element of a

int b[2][3];         // array of 2 arrays of 3 int
// int** p2 = b;     // error: b does not decay to int**
int (*p2)[3] = b;    // b decays to a pointer to the first 3-element row of b

int c[2][3][4];      // array of 2 arrays of 3 arrays of 4 int
// int*** p3 = c;    // error: c does not decay to int***
int (*p3)[3][4] = c; // c decays to a pointer to the first 3 × 4-element plane of c

Массивы с неизвестной границей

Если expr опущено в объявлении массива, объявленный тип является «массивом T с неизвестной границей» — разновидностью неполного типа, за исключением случаев, когда объявление используется с инициализатором агрегата:

extern int x[];      // the type of x is "array of unknown bound of int"
int a[] = {1, 2, 3}; // the type of a is "array of 3 int"

Поскольку элементы массива не могут быть массивами с неизвестной границей, многомерные массивы не могут иметь неизвестную границу ни в одном измерении, кроме первого:

extern int a[][2]; // okay: array of unknown bound of arrays of 2 int
extern int b[2][]; // error: array has incomplete element type

Если в той же области видимости имеется предшествующее объявление сущности, в котором граница была указана, то опущенная граница массива считается такой же, как в этом предыдущем объявлении. Аналогичное правило действует для определения статического члена данных класса:

extern int x[10];
struct S
{
    static int y[10];
};

int x[];               // OK: bound is 10
int S::y[];            // OK: bound is 10

void f()
{
    extern int x[];
    int i = sizeof(x); // error: incomplete object type
}

Можно создавать ссылки и указатели на массивы с неизвестной границей, но их нельзя(до C++20)и можно(начиная с C++20) инициализировать или присваивать им массивы и указатели на массивы с известной границей. Обратите внимание, что в языке программирования C указатели на массивы с неизвестной границей совместимы с указателями на массивы с известной границей, поэтому преобразование и присваивание допустимы в обоих направлениях.

extern int a1[];

int (&r1)[] = a1;  // okay
int (*p1)[] = &a1; // okay
int (*q)[2] = &a1; // error (but okay in C)

int a2[] = {1, 2, 3};
int (&r2)[] = a2;  // okay (since C++20)
int (*p2)[] = &a2; // okay (since C++20)

Указатели на массивы с неизвестной границей не могут участвовать в арифметике указателей и не могут находиться слева от оператора индексирования, но могут разыменовываться.

Массивы-rvalue

Хотя массивы нельзя возвращать из функций по значению и нельзя использовать в качестве операндов большинства выражений приведения типов, prvalue массива можно сформировать с помощью псевдонима типа, чтобы создать временный массив посредством функционального приведения с инициализацией фигурными скобками.

Подобно prvalue классов, prvalue массивов при вычислении преобразуются в xvalue посредством материализации временного объекта.

(начиная с C++17)

xvalue массива можно сформировать непосредственно, обратившись к члену-массиву rvalue класса, либо используя std::move или другое приведение типов или вызов функции, возвращающий ссылку на rvalue.

#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';           // size of the array
    f(X().i);                                    // okay: binds to xvalue
//  f(x.i);                                      // error: cannot bind to lvalue

    int a[2][3];
    f(std::move(a));                             // okay: binds to xvalue

    using arr_t = int[2][3];
    f(arr_t{});                                  // okay: binds to prvalue
    f(identity<int[][3]>{{1, 2, 3}, {4, 5, 6}}); // okay: binds to prvalue

}

Вывод:

24
24
24
24
24

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.

DR Применён к Опубликованное поведение Правильное поведение
CWG 393 C++98 указатель или ссылка на массив с неизвестной
границей не могли быть параметром функции
разрешено
CWG 619 C++98 если граница массива была опущена, её нельзя было
вывести из предшествующего объявления
вывод разрешён
CWG 2099 C++98 границу статического члена данных-массива нельзя было
опустить, даже если предоставлен инициализатор
опускать разрешено
CWG 2397 C++11 auto нельзя было использовать в качестве типа элемента разрешено

См. также

Документация по C о объявлении массива

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/array

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API