Array declaration
声明一个数组类型的对象。
目录 |
语法
数组声明是任何 声明符 具有以下形式的简单声明
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)
的
|
(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 文档
关于
数组声明
|