type arrayName [ arraySize ];
arraySize
是常量表达式,一般叫维度值。可以通过列表初始化,也可以先声明一个数组,在分别初始化每个元素。
int a[5]{ 1,2,3,4,5 };// 列表初始化,同 b c的效果一样
int b[5] = { 1,2 };
int c[] = { 1,2,3,4,5 };
int d[3];
int e[2][2] = {1, 2, 3, 4};
int f[2][2] = {{1, 2}, {3, 4}};
for (auto i = 0; i < size(d); i++) {// 分别初始化
d[i] = i + 1;
}
// 二维数组使用范围for语句,如果要改变遍历对象元素的值,要把循环变量定义成引用类型
int ia[10][10] = {0};
size_t cnt = 0;
for (auto &row : ia)
for (auto &col : row) {
++cnt;
col = cnt;
}
如果维度值大于初始化列表中的值总数量(如上面数组c),则用列表中的值初始化靠前的元素,剩余元素初始化成默认值。
【注】数组不允许直接拷贝和赋值,即不能将数组的内容拷贝给其他数组作为初始值,也不能将数组赋值给其他数组。
char a[3] = { 'C','+','+' };
char b[3] = a;// 错误
b = a;// 错误
当时用字符串字面值初始化字符数组时,需要注意字符串字面值的结尾处还有一个看不见的空字符'\n'
。如下面数组c
的维度值是4
,而非3
:
char a[] = { 'C','+','+' };// 维度是3
char b[] = { 'C','+','+','\0' };// 维度是4
char c[] = "C++";// 维度是4
char* d = c;
cout << d[0] << endl;
cout << d[1] << endl;
cout << d[2] << endl;
理解复杂声明的含义,需从数组的名字开始,按照由内向外,从右向左的顺序阅读。
int arr[10]{};
int* ptrs[10]{};// ptrs是数组,含有10个整型指针元素
int(*Parr)[10] = &arr;// Parr是指针,指向一个含有10个整型的数组
int(&Carr)[10] = arr;// Carr是引用,引用一个含有10个整型的数组
int *(*Pparr)[10] = &ptrs;// Pparr是指针,指向一个含有10个整型指针的数组
int *(&Cparr)[10] = ptrs;// Cparr是引用,引用一个含有10个整型指针的数组
数组除了大小固定之外,其他用法与vector基本类似。
int arrayA[10] = {1};
// &arrayA[10]数组最后一个元素的下一个指针
for (auto *i = arrayA; i != &arrayA[10]; i++) {
cout << *i << endl;
cout << *i;
}
// 根据数组长度遍历
for (auto i = 0; i < sizeof(arrayA) / sizeof(arrayA[0]); i++) {
cout << arrayA[i] << endl;
}
// 范围for语句,最舒服的一种姿势,可以改变i的值
for (auto i : arrayA) {
cout << i << endl;
}
// begin 和 end 是标准库函数
int *pB = begin(arrayA);
int *pE = end(arrayA); // 指向最后一个元素的下一个元素
for (auto i = pB; i < pE; i++) {
cout << *i << endl;
}
// 二维数组使用范围for语句,除了最内层的循环外,其余的控制变量都应该用引用类型
for (auto &row : ia) //
for (auto col : row)
cout << col << endl;
规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针, 但是下面几种情况例外:
sizeof
的操作数&
取数组的地址decltype
关键字的参数时typeid
的运算对象时因此如下语句int a[3]; int *p=a;
是可以正确编译执行的。在表达式中a
被解析为指向数组第一个元素的指针,那么赋值符号两边的类型匹配,因此可以正确编译执行。
规则2:下标总是与指针的偏移量相同:
C语言中将数组的下标改写成指针偏移量的主要原因在于指针和偏移量是底层硬件所使用的基本类型。如a[i]
中的i
总被编译器解析为偏移量,所以a[i]
总是被改写成*(a+i)
的形式,a
是指向数组第一个元素的指针,加上偏移量i
,表示该指针向后移i
个步长,然后取a+i
所在单元的内容。
由此就可以解释为什么C语言中数组的下标可以为负,C语言中不检查数组的下标是否越界同样跟这个有关,如下面这段程序:
int main(void) {
int a[3] = {1, 2, 3};
int *p = (a + 3);
printf("%d\n", p[-1]);
return 0;
}
程序执行结果为3,虽然下标为-1,但是被编译器解析为偏移量,因此相当于*(p-1)
。
规则3:在函数参数的声明中,数组名被编译器当做指向该数组第一个元素的指针。
在C语言中将形参的数组和指针等同起来是出于效率的考虑。假如不这么做,将整个数组的每个元素的值都拷贝一份进行传递,这样无论在时间上还是空间上的开销都可能是非常大的。但是又要能操作到数组中的元素,只需将数组第一个元素的地址传递给调用函数,然后通过指针去访问想要访问的空间,这样一来时空消耗将大大减少。因此在函数内部,编译器始终把参数中声明的数组名当做一个指向数组第一个元素的指针,这样一来,编译器可以产生正确代码,并不需要对数组和指针这两种情况作区分。因此void fun(int a[]);
和void fun(int *a)
两种形式的效果完全等同,在函数内部去引用a
的话,始终都会被编译器认为是指针。因为void fun(int a[]);
这种形式最终还是会被编译器解析为void fun(int *a);
这种形式告诉我们调用时必须传递一个指向整型数据的指针。
所以下面这段代码可以正确编译和执行:
void fun(int a[]) { printf("%d\n", a[0]); }
int main(void) {
int a[3] = {1, 2, 3};
int *p1, *p2;
int b = 4;
p1 = a;
p2 = &b;
fun(a); // 1
fun(&a[1]); // 2
fun(p1); // 1
fun(p2); // 4
fun(&b); // 4
return 0;
}
有数组:int a[3] = {1, 2, 3};
则对于这四个表达式:&p
,p
,a
,&a
&p:表示取存储指针变量p的内存单元的地址; sizeof(&p) = 8;
p:表示取指针变量p存储的地址; sizeof(p) = 8;
a:表示取数组第一个元素的地址,用在sizeof中是例外,不代表第一个元素的首地址;sizeof(a)=3 * 4= 12;
&a:表示取整个数组的首地址; sizeof(&a) = 8
虽然a
和&a
的值相同,但是所表达的含义完全不同,a
表示取数组第一个元素的地址,而&a
表示取数组的首地址。它们所代表的类型也完全不同,a
是一个int
型指针,而&a
是一个int (*p)[]
型指针,即数组指针。所以a+1
和&a+1
得到的结果不同,a+1
表示将指向该数组的第一个元素的指针向后移一个步长(这里的步长为数组元素类型所占的字节数);而&a+1
表示将指向该数组的指针向后移动一个步长(而此处的步长为数组元素个数
* 元素类型所占的字节数
)。
数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。定义: int (*p)[n];
()
优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int(*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p = a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
所以数组指针也称指向一维数组的指针,亦称行指针。
指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。定义: int *p[n];
[]
优先级高,先与p结合成为一个数组,再由int*
说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1
时,则p指向下一个数组元素,这样赋值是错误的:p=a;
因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1]
,而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a;
这里*p
表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
for (i = 0; i < 3; i++)
p[i] = a[i];
这里int *p[3]
表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。
【注】还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。比如要表示数组中i
行j
列一个元素,优先级:()>[]>*
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]
多维数组的数组名,代表的是第一维数组的首元素地址,如int ia[3][4];
中ia
是第一维数组int_array ia[3];
的首元素地址,其中using int_array = int[4];
类型别名的使用有两种:
using int_array = int[4];
// 新标准下类型别名的声明typedef int int_array[4];
// 等价的typedef
声明 // 下面两个有一个就OK
using int_array = int[4];
// typedef int int_array[4];
int ia[3][4] = {};
size_t cnt = 0;
for (int_array *p = ia; p != ia + 3; ++p)
for (int *q = *p; q != *p + 4; ++q) {
cnt++;
*q = cnt;
}
for (int_array *p = ia; p != ia + 3; ++p)
for (int *q = *p; q != *p + 4; ++q) {
cout << *q << endl;
}
这个总结针不戳:二维数组名和二级指针