命名空间
变体
操作

数组声明

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (在 C++17 中弃用*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

声明一个数组类型的对象。

内容

[编辑] 语法

数组声明是任何简单声明,其声明符具有以下形式

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)、指针指向成员的指针枚举或来自其他已知边界的数组(在这种情况下,数组被称为多维数组)构建。换句话说,只有对象类型(数组类型除外)才能作为数组类型的元素类型。不完整元素类型的数组类型也是不完整类型。

可能受约束的(自 C++20 起) auto 说明符可以用作指向数组的指针或引用的声明中的数组元素类型,它从初始化器或函数参数(自 C++14 起)推导元素类型,例如,如果 a 是类型为 int[42] 的左值,则 auto (*p)[42] = &a; 有效。

(自 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

[编辑] 赋值

数组类型的对象不能作为一个整体进行修改:即使它们是左值(例如,可以获取数组的地址),它们也不能出现在赋值运算符的左侧

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

[编辑] 数组到指针的退化

存在从数组类型的左值和右值到指针类型右值的隐式转换:它构造一个指向数组第一个元素的指针。当数组出现在不期望数组但期望指针的上下文中时,会使用此转换

#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)

指向未知边界数组的指针不能参与指针算术,也不能在下标运算符的左侧使用,但可以被解引用。

[编辑] 数组右值

尽管数组不能从函数按值返回,也不能作为大多数强制类型转换表达式的目标,但可以使用类型别名来构造数组临时量,从而形成数组纯右值,方法是使用花括号初始化的函数式强制类型转换

与类纯右值一样,数组纯右值在求值时通过临时量实质化转换为将亡值。

(自 C++17 起)

数组将亡值可以直接通过访问类右值的数组成员或使用 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';           // 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 文档 关于 数组声明