数组 Array
AR-1 在数组中使用指针
尽管C的数组和指针概念十分相似,但不能完全将其等价。例如考虑以下两个声明:
int a[5];
int *b;
第一个声明int a[5];
实际上在声明的同时立即在内存中分配了整型数组的内存空间以及整型指针的内存空间。
而第二个声明int *b;
仅仅只分配了一个整型指针的内存空间。
int a[5];
*(a + 2) = 1;
*(a + 2)
是什么?其实它的用法与a[2]
相同。a
是数组名的同时也是一个指针常量,a+2
会根据a
的类型(指向int
的指针),对2
做出调整,得到的结果是指向“a
指向的位置后移2个int
型变量的位置”的指针。
int array[10];
int *ptr = array + 2 ;
此时容易看出int
型指针ptr
现在指向的是a[2]
。对于ptr
依然可以使用下标访问数组中的其他元素,例如:ptr[1]
是a[3]
,而ptr[-1]
是a[1]
(下标可以是负整数,即向后偏移了若干个“单位”)。下标内的值(或表达式)看做一个偏移量。
在正确使用的前提下,使用指针的运行效率一定不低于使用下标。但是频繁使用指针可能会降低代码的可读性。
AR-2 多维数组
普通的数组是一个一维线性的数据结构。那么能否创建一个不止一维的数组?
int matrix[3][4];
我们可以说matrix
是一个二维数组,但是实际上,计算机并不是真的在“二维平面”上存储这个数组,而是将3个int [4]
样式的数组并在一起,或者不如说:创建了三个分别包含4个元素的数组,以及一个包含三个元素的“指针数组”。
我们可以像使用普通数组元素那样使用一个二维数组中的元素,只是需要使用多个下标来确定一个元素,如:matrix[1][2] = 1;
。
那么这个多维数组的数组名matrix
单独出现时,有着怎样的含义?其实,就如同一维数组的数组名是指向元素的指针一样,二维数组的数组名是指向数组的指针。所以,matrix
的值是一个指向包含4个int
元素的数组的指针。
因此,表达式matrix + 1
其实也是指向数组的指针,但是在此处偏移量1
根据它的元素类型(包含4个int
的数组)进行了调整,因此指向的是数组的“下一行”。
*(matrix + 1)
对一个指向数组的指针进行解引用,得到的当然是数组名,即一个指针。之后再进行一次解引用*(*(matrix + 1))
得到的才是这个二维数组中的元素。这等同于连续使用两次[]
下标来访问某一个元素。在绝大多数情况下使用下标明显要直观很多。
那么,我们能否直接声明一个指向数组的指针?
AR-3 多维数组与指向数组的指针
int array[5];
int *p = array;
int (*ptr) [5];
在这个例子中,array[5]
是一个数组。p
是一个指向int
型变量的指针,对p
解引用得到的是array[0]
。那么ptr
呢?
答案是,ptr
是一个指向了整型数组的指针。对ptr
进行解引用,得到的是一个数组名。准确来说,得到的是一个包含有5个int
型元素的数组名。
这启发我们将其与上面多维数组的概念联系起来:
int matrix [3][5];
int (*ptr) [5];
既然ptr
是一个指向数组的指针,而单独出现的matrix
也是指向数组的指针,那么它们应当是同一种类型的:
ptr = matrix;
指针ptr
现在指向这个二维数组的第一个一维数组,或者也不妨理解为:指向了matrix
的第一行。
AR-4 使用多维数组
多维数组可以在声明时初始化,只是形式会明显更加复杂:
int matrix [2][3]={1,2,3,4,5,6};
/*
*/
int matrix [2][3]={
{1,2,3},
{4,5,6}
};
第二种初始化使用的缩进没有实际意义,只是让它看起来“像”一个2行3列的矩阵。也可以写成这样,取决于你的理解:
int matrix [2][3]={{1,2,3},{4,5,6}};//当然大括号是可以去掉的
其实,不妨将matrix
“想象”成一个只包含了2个元素的“数组”,而这个数组的组成元素也同样是:数组。这样的理解有助于我们清楚理解下面的函数调用:
int array [5] = {1,2,3,4,5};
int matrix [3][5] = {{1,2,3,4,5},{1,2,3,4,5},{1,2,3,4,5}};
func1(array);
func2(matrix);
func1
的函数原型是void func1(int *array)
或者void func1(int array[])
,函数在传值调用中传递的值正是array
这一数组名代表的指针常量。
func2
的事情比较复杂,因为它需要的形参类型是一个指向数组的指针。
很容易想到下面这种:
void func2(int mat[3][5]);
但是我们可以将matrix
看做一个包含3个元素,其中每个元素是包含了5个int
元素的整型数组。对于传递数组的形参而言,数组的元素个数无关紧要,重要的是数组中元素的类型。所以可以是:
void func2(int mat[][5]);//忽略首先出现的元素个数
或者:
void func2(int (*mat)[5]);//对mat解引用,得到int [5]:意味着声明了一个指向数组的指针
因为方括号[]
的运算符优先级是高于*
的优先级的,所以此处必须使用括号。
在传递参数(数组名)之后,就可以在函数中像一维数组那样,正常使用数组名+下标(可能是多个下标)访问内存中的各个元素了。
AR-5 指针的数组
指针变量和其他变量并没有什么巨大的差异,自然地,使用数组来储存一系列的相关的指针也是可行的,但是要尤其注意(有时是十分混乱的)C风格声明,不要在“指向数组的指针”“指针的数组”之间产生混淆。
int *p[5];//指针的数组
int (*p)[5];//指向数组的指针
在此处起到了关键性作用的是运算符的优先级。
正如前面所提到的那样,下标[]
的优先级更高。在第一个声明中,int *p[5];
首先声明了一个数组,而数组中每个元素类型是int *
,即:指向int
的指针的数组。而int (*p)[5];
,如前所述,声明了一个指针,它指向的是含有5个int
型变量的数组,所以这是一个指向数组的指针。
指针的数组可以帮助我们储存若干个字符串常量:
char const *string_ptr_array[] = {"do","for","if","register"}
string_ptr_array
是一个指针数组,在这里,我们利用这些数组中的指针储存了字符串常量的地址,作为char const *
类型,以便于在(之后的)字符串函数中作为参数进行使用。
本系列博客为我本人原创的学习笔记,尽量勤更新,如有错误欢迎各位大佬指出,Thanks♪(・ω・)ノ