数组声明
声明一个数组类型的对象。
内容 |
[编辑] 语法
数组声明是任何简单声明,其 声明符 具有以下形式
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) |
(自 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 文档 对于 数组声明
|