int arr[5];
int *ptr = arr;
一、数组与指针
当我们创建一个数组时,需要规定元素类型、数组名以及数组的大小,然后程序会申请一块连续的内存用于存放数组。
数组名代表了数组首个元素的地址,这是一个十分关键的概念。基于这个概念,我们很自然都就得出下面这组关系:
&arr[i] <=> arr+i
arr[i] <=> *(arr+i)
似乎这样来看数组名就是指针,但是不像指针变量可以进行赋值操作,数组名代表一个数组的首个元素的地址,因此数组名应当被看作一个常量,无法进行赋值操作。想象你创建了一个数组,但是数组名的值被改变了,我们再想要对数组中的元素进行操作时,已经找不到当初数组创建时的那块连续的内存,因此数组名虽然是一个地址,但是其本身不能被修改。
ptr++; //valid
arr++; //invalid
二、数组作为函数参数
当数组作为函数的参数传入时,编译器并不会复制一份数组的副本传入函数,取而代之是将数组的首个元素的地址传入函数。这是因为数组可能很大,复制一个数组的副本对内存并不友好。下面两种写法没有区别,或者说写法二更加准确,因为编译器会将写法一解释为写法二。
int func(int A[]){ //写法一
}
int func(int* A){ //写法二
}
三、指针和字符数组
在C语言中,字符串以字符数组的形式存在。数组大小 = 字符串中字符的个数+1,这是因为需要最后一个元素等于 '\0' 作为标识,代表字符串结束。
此处的 '\0' ,是字符串类型,占用一个Byte,ASCII值是0;注意区分整数 0 ,int类型,一般占4个Bytes,以及'0',字符0,ACSII值是48。
下面是字符数组的一些初始化方式:
char C[6] = {'H','e','l','l','o','\0'}; //数组大小最少是6
char C[] = "Hello"; //数组大小为6 编译器会自动在末尾补上'\0'
char C[20] = "Hello"; //数组大小为20 编译器会自动在C[5]补上'\0'
//char C[5] = "Hello"; //invalid 数组大小不够
四、指针和二维/多维数组
1、二维数组
二维/多维数组在内存中仍是占用连续的一块内存。
int B[3][4];
数组名是数组首个元素的首地址,这点十分重要。对于二维数组B来说,B有3个元素,B[0]、B[1]、B[2],而这3个元素都是拥有4个元素的一维数组。
结合图片,理解上面两点应该更加清晰。当数组是二维甚至多维时,指针的使用需要注意。数组名B,表示的是B的首个元素的首地址,而B的首个元素是B[0],所以B等于B[0]的首地址。此时如果我们希望通过一个指针保存B,需要注意指针的类型。
int B[3][4];
//int *ptr1 = B; //invalid
int (*ptr)[4] = B; //valid
指针的类型很重要,因为指针在解引用时,需要知道内存里存储的数据类型,只有这样才能正确解引用内存里的数据。
而B中存储的元素是一维数组,并且是拥有四个元素的一维数组,并不是整型。因此,可以存储B的地址的指针类型为 int (*)[4] 。
对于数组B来说,*B <=> B[0] <=> &B[0][0] ,这三者是相同的。结合指针算术运算的知识,我们也能得出,*(B+1) <=> B[1] <=> &B[1][0] 。其中B和(B+1)对应的指针类型为int (*)[4],B[0]和B[1]对应的指针类型为int *。
对于一个二维数组 B[i][j] = *(B[i]+j) = *(*(B+i)+j) 。
2、多维数组
多维数组实际上是数组的数组,比如三维数组就是拥有若干个二维数组为元素的数组。在解引用操作时,套用了更多层,使用的时候要注意。多维数组的初始化方式:
int C[3][2][2] = {{{2,5},{3,9}},
{{3,4},{6,5}},
{{1,0},{5,7}}};
需要注意一下,当函数接收二维或多维数组时的写法。
//一维数组
void func(int A[]){
}
void func(int *A){
}
//二维数组,最底层的一维数组的大小不能被省略
void func(int A[][2]){
}
void func(int (*A)[2]){
}
//三维数组,最底层的一维数组的大小不能被省略
void func(int A[][2][2]){
}
void func(int (*A)[2][2]){
}