数组声明
声明一个数组类型的对象。
目录 |
[编辑] 语法
数组声明是任何简单的声明,其声明符具有以下形式
noptr-declarator [ expr (可选)] attr (可选) |
|||||||||
noptr-declarator | - | 任何有效的声明符,但如果它以* 、& 或&& 开头,则必须用括号括起来(否则整个声明符被视为指针声明符或引用声明符)。 |
expr | - | 一个整型常量表达式(直到 C++14)一个类型为std::size_t的转换常量表达式(从 C++14 开始),其计算结果大于零 |
属性 | - | (从 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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 393 | C++98 | 指向未知边界数组的指针或引用 不能作为函数参数 |
允许 |
CWG 619 | C++98 | 当省略数组边界时,不能 从之前的声明中推断出来 |
允许推断 |
CWG 2099 | C++98 | 即使提供了初始化器,也不能省略 数组静态数据成员的边界 |
允许省略 |
CWG 2397 | C++11 | auto不能用作元素类型 | 允许 |
[编辑] 参阅
C 文档,关于数组声明
|