在C++中,内置数组属于复合类型的一种,数组元素的个数属于其类型的一部分。
也就是说,数组元素的个数必须在编译时就确定,可以通过无符号整型的字面量或常量表达式进行指定,也可以交由编译器根据初始化列表的元素个数进行推断。
constexpr int arraySize = 5;
int arraySize2 = 5;
int a[5]; // √
int b[arraySize]; // √
int c[arraySize2]; // error: array bound is not an
// integer constant before ']' token
内置数组的初始化分为两种情况:
int a[5]; // 所有元素初始化为0
void func(){
int b[5]; // 元素值未定义
}
int a[] = {1, 2, 3, 4, 5}; // int[5]
int b[5] = {1, 2, 3, 4}; // b[4] = 0
遵循遵循”自内向外,先右后左“的顺序。
如果需要声明一个指向数组的指针或者绑定数组的引用,需要添加括号,否则变量名均会先向右绑定数组维度,使前面的指针符号用于组成数组元素的类型。
int *a[10]; // 存有10个int指针的数组
int (*b)[10]; // 指向一个长度为10的数组指针
int &c[10]; // error, 不存在引用的数组
int (&d)[10]; // 一个长度为10的数组的引用
内置数组本身是一种类型,映射向内存上的一块连续存储空间。只是我们在操作数组时,数组名会被转化为数组首元素的地址,其类型是数组元素类型的指针。
这里补充下,这本质上是一种隐式类型转换。但是当数组名作为decltype
的参数,或者&
、sizeof
运、typeid
运算符的运算对象时,上述隐式转换不会发生。
在使用类型推断说明符时,auto
会将初始值为数组名的变量推断为元素类型指针,只有将变量名用引用符修饰,才会得到目标数组的引用类型。而使用decltype
推断数组名,将直接得到数组类型。
int a[5] = {1, 2, 3, 4, 5};
auto p_a = a; // p_a 为 int*
auto &r_a = a; // r_a 为 int[5]
decltype(a) c; // c 为 int[5]
数组名在程序中会被视为数组首元素地址,是一个数组元素类型的指针。指针是一种迭代器,通过数组名,我们可以得到其中任意一个元素的地址。
int a[5] = {1, 2, 3, 4, 5}; // 假设int类型大小为4个字节
int &b = *(a + 4); // 则可计算得到数组中第5个元素的引用
下标运算符[]
原理与之相同,a[4]
等价于*(a + 4)
。
然而,与vector
这种容器的下标运算符不同,内置数组类型的下标可以为负数,此时依旧等价于对目标指针执行加减运算。如果该指针为数组名,则负数下标对于该数组而言无意义。然而,如果指针指向的是数组中间的某个元素,可以通过负数下标,得到该元素之前的数组元素。
int a[5] = {1, 2, 3, 4, 5};
int *b = &a[4]; // b为a第5个元素的指针
int &c = b[-1]; // c为a第4个元素的引用,b[-1] 等价于 *(b - 1)
int a[5] = {1, 2, 3, 4, 5}; // 定义一个长度为5的整型数组
for(int i = 0; i < 5; i++){
// ... ...
};
for(int &i : a){
// ... ...
}
与STL容器相同,可以结合首迭代器与尾后迭代器来遍历数组。通过std
命名空间中的begin
与end
函数获取内置数组的首尾迭代器,得到的迭代器类型为指针。(指针本身就是迭代器的一种,属于随机访问迭代器)
for(int *i = begin(a); i < end(a); i++){
// ... ...
}
多维数组不属于内置类型的一种,其本质上是数组的数组。
注意两点:
int a[3][4]; //
在上面的定义语句里,首先a是一个有三个元素的数组,然后每个元素又都是一个具有四个元素的数组,并且数组的元素类型为int。
for(auto &i : a){ // 务必将i推导为数组类型,而不能是指针类型。
for(auto &j : i){
// ... ...
}
}