数组声明
声明数组类型对象。
目录 |
[编辑] 语法
数组声明可以是任何声明器拥有如下形式的简单声明
noptr-declarator [ expr(可选) ] attr(可选)
|
(1) | ||||||||
noptr-declarator | - | 任何合法 declarator ,但若它以 * 、 & 或 && 开始,则它必须为括号所环绕。 |
attr(C++11) | - | 属性的可选列表 |
expr | - | 类型的整数常量表达式 (C++14 前) std::size_t 受转换常量表达式 (C++14 起),求值为大于零的值 |
拥有形式 T a[N]; 的声明,声明 a
为由 N
个连续分配的 T
类型对象组成的数组对象。数组元素拥有编号 0, …, N - 1
,且能通过下标运算符 [] 访问,如以 a[0], …, a[N - 1] 。
数组可由任何基础类型(除了 void )、指针、指向成员的指针、类、枚举,或从其他数组类型(这种情况下我们说数组是多维的)构造。无引用的数组、函数的数组或抽象类类型的数组。
应用 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 <numeric> #include <iterator> 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); // error 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); // 能以行为主布局视作 2 × 3 矩阵 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}, {4, 5, 6}}; // 能以行为主布局视作 2 × 3 矩阵
注意应用数组到指针退化时,多维数组转换成指向其首元素的指针(例如,指向其首行或首平面的指针):数组到指针退化仅应用一次。
int a[2]; // 2 个 int 的数组 int* p1 = a; // 退化到指向 a 首元素的指针 int b[2][3]; // 2 个 3 个 int 的数组的数组 // int** p2 = b; // 错误: b 不退化到 int** int (*p2)[3] = b; // b 退化到指向 b 首个 3 元素行的指针 int c[2][3][4]; // 2 个 3 个 4 个 int 的数组的数组的数组 // int*** p3 = c; // 错误: c 不退化到 int*** int (*p3)[3][4] = c; // c 退化到指向 c 首个 3 × 4 元素平面的指针
[编辑] 未知边界数组
若在数组声明中省略 expr ,则声明的类型是“ 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][]; // 错误:数组有不完整类型
可以构成到未知边界数组的引用和指针,但不能从数组或指向已知边界数组的指针初始化或赋值。注意在 C 程序语言中,指向未知边界数组的指针与指向已知边界数组的指针兼容,从而可以双向转换、赋值。
extern int a1[]; int (&r1)[] = a1; // OK int (*p1)[] = &a1; // OK int (*q)[2] = &a1; // 错误(但 C 中 OK ) int a2[] = {1, 2, 3}; int (&r2)[] = a2; // 错误 int (*p2)[] = &a2; // 错误(但 C 中 OK )
指向未知边界数组的指针不能参与指针算术且不能用在下标运算符的左侧,但可以解引用。到未知边界数组的指针和引用不能用于函数参数 (C++14 前)。
[编辑] 数组右值
尽管数组无法从函数以值返回,且不能作为大多数转型表达式的目标,数组纯右值亦可通过使用类型别名组成,以用花括号初始化函数转型构造数组临时量。
同类纯右值,数组纯右值由临时量实质化在求值时转换成亡值。 | (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'; // 数组的大小 f(X().i); // OK :绑定到亡值 // f(x.i); // 错误:不能绑定到左值 int a[2][3]; f(std::move(a)); // OK :绑定到亡值 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