Namespaces
Variants

Array declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

目录

语法

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

noptr-declarator [ expr  (可选) ] attr  (可选)
noptr-declarator - 任意有效的 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 外)、 指针 成员指针 枚举 或已知边界的其他数组(此时该数组称为多维数组)构建。换言之,只有对象类型(未知边界数组类型除外)才能作为数组类型的元素类型。不完整元素类型的数组类型同样属于不完整类型。

可能被 约束 (since C++20) auto 说明符可用于声明指向数组的指针或引用时的数组元素类型,该说明符会从初始化器 或函数参数 (since C++14) 推导元素类型,例如:若 a 是类型为 int [ 42 ] 的左值,则 auto ( * p ) [ 42 ] = & a ; 是有效的。

(since C++11)

不存在引用数组或函数数组。

对数组类型应用 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; // 正确:可以获取a的地址
a = b;            // 错误:a是数组
struct { int c[3]; } s1, s2 = {3, 4, 5};
s1 = s2; // 正确:隐式定义的复制赋值运算符
         // 可以赋值数组成员类型的数据成员

数组到指针退化

存在从数组类型的左值和右值到指针类型右值的 隐式转换 :该转换会构造指向数组首元素的指针。每当数组出现在需要指针而非数组的语境中时,就会使用此转换:

#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); // 正确:函数通过引用接收数组
//  g(p); // 错误
    for (int n : a)            // 正确:数组可用于范围for循环
        std::cout << n << ' '; // 输出数组元素
//  for (int n : p)            // 错误
//      std::cout << n << ' ';
    std::iota(std::begin(a), std::end(a), 7); // 正确:begin和end函数接受数组
//  std::iota(std::begin(p), std::end(p), 7); // 错误
    // 在需要指针但数组不可用的场景中,两者均可使用:
    f(a); // 正确:函数接收指针
    f(p); // 正确:函数接收指针
    std::cout << *a << '\n' // 输出第一个元素
              << *p << '\n' // 相同
              << *(a + 1) << ' ' << a[1] << '\n'  // 输出第二个元素
              << *(p + 1) << ' ' << p[1] << '\n'; // 相同
}

多维数组

当数组的元素类型是另一个数组时,该数组被称为多维数组:

// 由2个包含3个整数的数组组成的数组
int a[2][3] = {{1, 2, 3},  // 可视为2×3矩阵
               {4, 5, 6}}; // 采用行优先布局

请注意,当发生数组到指针的衰减时,多维数组会转换为其首元素的指针(例如,指向其首行或首平面的指针):数组到指针的衰减仅应用一次。

int a[2];            // 包含2个整数的数组
int* p1 = a;         // a 退化为指向其首元素的指针
int b[2][3];         // 包含2个数组的数组,每个数组包含3个整数
// int** p2 = b;     // 错误:b 不会退化为 int**
int (*p2)[3] = b;    // b 退化为指向其首行(3元素数组)的指针
int c[2][3][4];      // 包含2个数组的数组,每个数组包含3个数组,每个数组包含4个整数
// int*** p3 = c;    // 错误:c 不会退化为 int***
int (*p3)[3][4] = c; // c 退化为指向其首平面(3×4元素数组)的指针

未知边界数组

如果在数组声明中省略了 expr ,则声明的类型是“未知边界T数组”,这是一种 不完整类型 ,除非在带有 聚合初始化 的声明中使用时例外:

extern int x[];      // x的类型是“未知大小的int数组”
int a[] = {1, 2, 3}; // a的类型是“3个int的数组”

由于数组元素不能是未知边界的数组,多维数组除第一维外其他维度不能具有未知边界:

extern int a[][2]; // 正确:未知大小的数组,其元素为包含2个int的数组
extern int b[2][]; // 错误:数组具有不完整的元素类型

如果在指定数组界限的同一作用域中存在该实体的前置声明,则被省略的数组界限会被视为与先前声明中的界限相同,对于类的静态数据成员定义也同样适用:

extern int x[10];
struct S
{
    static int y[10];
};
int x[];               // 正确:边界为10
int S::y[];            // 正确:边界为10
void f()
{
    extern int x[];
    int i = sizeof(x); // 错误:不完整的对象类型
}

可以形成指向未知边界数组的引用和指针, 但不能 (C++20 前) 且能 (C++20 起) 从已知边界数组及指向已知边界数组的指针进行初始化或赋值。注意在C编程语言中,指向未知边界数组的指针与指向已知边界数组的指针兼容,因此支持双向转换和赋值。

extern int a1[];
int (&r1)[] = a1;  // 正确
int (*p1)[] = &a1; // 正确
int (*q)[2] = &a1; // 错误(但在C语言中正确)
int a2[] = {1, 2, 3};
int (&r2)[] = a2;  // 正确(自C++20起)
int (*p2)[] = &a2; // 正确(自C++20起)

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

数组右值

虽然数组不能通过值从函数返回,且不能作为大多数强制转换表达式的目标,但可以通过使用类型别名结合 花括号初始化函数式转换 来构造数组临时对象,从而形成数组 纯右值

与类纯右值类似,数组纯右值在被求值时通过 临时物化 转换为亡值。

(since 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);                                    // 正确:绑定到xvalue
//  f(x.i);                                      // 错误:无法绑定到lvalue
    int a[2][3];
    f(std::move(a));                             // 正确:绑定到xvalue
    using arr_t = int[2][3];
    f(arr_t{});                                  // 正确:绑定到prvalue
    f(identity<int[][3]>{{1, 2, 3}, {4, 5, 6}}); // 正确:绑定到prvalue
}

输出:

24
24
24
24
24

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 应用于 发布时行为 正确行为
CWG 393 C++98 指向未知边界数组的指针或引用
不能作为函数参数
允许
CWG 619 C++98 省略数组边界时,无法从
先前的声明推断边界
允许推断
CWG 2099 C++98 即使提供了初始化器,静态数据成员
的数组边界也不能省略
允许省略
CWG 2397 C++11 auto 不能用作元素类型 允许

参见

C 文档 关于 数组声明