TYPE name[elements]
TYPE:必须指定数组类型,不允许用auto关键字推断数组的类型。数组的元素必须是对象,而引用不是对象,因此不存在引用的数组,指针本身就是一个对象,因此存在指针的数组。
elements:数组的维度,编译的时候应该是已知的,所以维度必须是一个常量表达式(列表初始化时允许忽略),默认情况下数组的元素被默认初始化(可能含未定义的值,如果是某种内置类型的数组)。
unsigned cnt = 32;
constexpr unsigned sz = 32;
int arr[10]; // 含10个整数的数组
int *parr[sz]; // 含32个整型指针的数组
double bad[cnt]; // 错误:cnt不是常量表达式
char strs[get_size()]; // 当get_size()是constexpr时正确;否则错误
数组的初始化
'\0'
也会被拷贝到字符数组中。const unsigned sz = 3;
int iarr[sz] = {1, 2, 4}; // 含三个元素的数组,元素值分别为1,2,4
在内存中的表示为:
value | 1 | 2 | 4 |
---|---|---|---|
index | 0 | 1 | 2 |
int iarr1[] = {1, 2, 4}; // 维度是3
int iarr2[3] = {1, 2, 4, 8}; // 错误:初始值过多
char carr[] = {'C', '+', '+'}; // 列表初始化,没有空字符串
char carr1[] = {'C', '+', '+', '\0'}; // 列表初始化,含显式空字符串
char carr2[] = "C++"; // 字符串字面值初始化,自动在结尾处添加空字符串,维度是4
carr2在内存在的表示:
value | C | + | + | \0 |
---|---|---|---|---|
index | 0 | 1 | 2 | 3 |
// 下面操作将引发错误
// error: initializer-string for array of chars is too long
const char carr3[3] = "C++"; // 错误:没有空间可存放空字符
// error: array must be initialized with a brace-enclosed initializer
char carr4[] = carr; // 错误:不允许使用一个数组初始化另一个数组
// error: invalid array assignment
carr4 = carr; // 错误:不允许把一个数组直接赋值给另一个数组
指针的数组,数组的指针和数组的引用
int iarr[3] = {};
int *parr[3]; // 这是一个数组,元素是int *类型
// 可以看成 (int*) parr[3]
int (*aptr)[3] = &iarr; // aptr是一个指针,指向一个含有3个元素的数组
// 可以看成 (int[3]) *aptr
int (&aref)[3] = iarr; // aref是一个引用,绑定一个含有3个元素的数组
// 可以看成 (int[3]) &aref
// 注意不可写成 int &aref[3],不存在引用的数组
// 可能没什么用的一个比较复杂的声明
int *(&val)[3] = v;
// 可以看成 (int*)[3] &val
// &val是一个引用,绑定一个 int *[3]
// int *[3] 是一个数组,元素是指针类型
// int * 指针指向int型元素
访问数组元素
可以使用范围for或者下标运算符([]
)来访问数组中的元素。数组的下标运算符是由C++语言直接定义的,带符号,允许处理负值。
数组的索引是从0开始的。通常将数组下标定义为size_t
类型(机器相关,无符号,且被设计得足够大以表示内存中任意对象的大小),定义在cstddef
头文件中。
举个栗子:用数组记录各分数段成绩个数:
// scores.cpp
#include
int main() {
// 以10 为一个分数段进行统计: 0-9, 10-19, ..., 90-99, 100
unsigned scores[11] = {}; // 列表初始化为0
unsigned grade = 0;
// 使用下标访问数组元素
while(std::cin >> score) {
if (grade <= 100) {
++scores[grade / 10]; // 当前分数段计数加1
}
}
// 使用范围for输出各分数段的个数
for (auto i : scores) {
std::cout << i << std::endl;
}
return 0;
}
编译运行结果:
$ ./score.out
12 56 78 34 90 33 98 67 E
0
1
0
2
0
1
1
1
0
2
0
注意,编译器不负责检查数组下标。当数组的下标越界并尝试访问非法内存时,就会发生缓冲区溢出错误。
数组和指针
在用到数组名字的地方,编译器会自动将其替换为一个指向数组首元素的指针。在一些情况下数组的操作实际上是指针的操作。
使用指针可以遍历数组中的元素,前提是先获取到指向数组第一个元素的指针和指向尾元素下一个位置的指针。指向首元素的指针可以通过数组名字和首元素的地址获得,尾后指针通过以下方式获得:
int iarr[4] = {};
int *e = &iarr[4];
尾后指针不指向具体元素,因此不可以对尾后指针执行解引用或递增操作。
当使用一个数组作为一个auto变量的初始值时,推断的类型是指针而非数组。而当使用decltype()返回的是一个数组。
int iarr[4] = {};
auto iarr1(iarr); // 实际上是执行了 auto iarr1(&iarr[0]),iarr1是一个int*
// 下面操作将发生错误
// error: invalid conversion from 'int' to 'int*'
iarr1 = 32;
decltype(iarr) iarr2; // iarr2是一个含4个元素的数组
iarr2[2] = 3;
数组的指针支持的运算:
*ptr
:解引用,获得ptr所指的对象++ptr
:指向ptr的下一个元素;--ptr
:指向ptr的上一个元素;ptr1 == ptr2
:ptr1和ptr2是否指向同一个位置或同一个尾后指针;ptr1 != ptr2
:ptr1和ptr2是否指向同一个位置或同一个尾后指针;ptr + n
:加上一个整数仍得到一个指针,新位置与原来相比向前移动了n个位置;ptr - n
:减去一个整数仍得到一个指针,新位置与原来相比向后移动了n个位置;ptr += n
:指针向前移动n个位置;ptr -n
:指针向后移动n个位置;ptr1 - ptr2
:指针相减得到指针之间的距离,类型为ptrdiff_t,类似size_t,ptrdiff_t也是定义在cstddef头文件中的机器相关的类型;ptr1 >(>=/<=) ptr2
:如果ptr1指向的位置在ptr2指向的位置之前,则ptr1小于ptr2;参与运算的两个指针必须指向同一个数组中的元素,或者尾元素的下一个位置。int ival = 3, sz = 44;
int *ip = &ival, *e = sz;
while( p> e) { // 未定义的:p和e无关,比较毫无意义
}
标准库函数begin和end(C++11)
template(class T, std::size_t N)
T* begin(T (&array)[N]);
T* end(T (&array)[N]);
auto n = end(arr) - begin(arr);
练习:利用指针将数组元素置0。
// setZero.cc
#include
void display(int *arr, int n) {
for(int i = 0; i != n; ++i) {
std::cout << *arr << std::endl;
++arr;
}
}
int main() {
int iarr[] = {3, 6, 7, 9};
display(iarr, 4);
// 将元素置0
for(auto &i : iarr) {
i = 0;
}
display(iarr, 4);
return 0;
}
编译运行结果:
$ ./setZero.cc
3
6
7
9
0
0
0
0
练习:比较两个数组对象是否相等。
// compare.cc
#include
bool compare(int *a1, int n1, int *a2, int n2) {
int n = n1 > n2 ? n1 : n2;
bool isequal = true;
for (int i = 0; i != n; ++i) {
if (*a1 != *a2) {
isequal = false;
break;
}
++a1;
++a2;
}
}
int main() {
int a1 = {1, 3, 5, 7};
int a2 = {1, 3, 5};
int a3 = {1, 2, 5, 7};
int a4 = {1, 3, 5, 7};
std::cout << "Is a1 equal to a2 : " << compare(a1, 4, a2, 3) << std::endl;
std::cout << "Is a1 equal to a3 : " << compare(a1, 4, a3, 4) << std::endl;
std::cout << "Is a1 equal to a4 : " << compare(a1, 4, a4, 4) << std::endl;
reture 0;
}
$ ./compare.out
Is a1 equal to a2 : 0
Is a1 equal to a3 : 0
Is a1 equal to a4 : 1