指针与数组:
数组指针
一、利用数组名遍历数组元素
数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示:
定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:
(例1_1 使用数组名作为指针遍历数组元素)
二、利用指针遍历数组元素
1、指向数组开头的指针
我们也可以定义一个指向数组的指针,例如:
int arr[] = { 99, 15, 100, 888, 252 };
int *p = arr;
arr 本身就是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的。但是特别注意的是,“arr 本身就是一个指针”这种表述并不准确,严格来说应该是“arr 被转换成了一个指针”。区别在于:sizeof(arr)和sizeof(p)。
定义:如果一个指针指向了数组,我们就称它为数组指针(Array Pointer)。
数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关,上面的例子中,p 指向的数组元素是 int 类型,所以 p 的类型必须也是int *。反过来想,p 并不知道它指向的是一个数组,p 只知道它指向的是一个整数,究竟如何使用 p 取决于程序员的编码。
(例1_2使用数组指针来遍历数组元素)
注意:在求数组的长度时不能使用sizeof(p) / sizeof(int),因为 p 只是一个指向 int 类型的指针,编译器只知道它指向的是一个整数但不知道是一个整数(数组),所以 sizeof(p) 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。
2、指向数组某个位置的指针
上节我们讲到,对指针变量进行加法和减法运算时,是根据数据类型的长度来计算的。p+1 指向下一个元素,p-1 指向上一个元素。那么p+i 指向下 i 个元素,p -i 指向上 i 个元素。(例1_3 p 指向数组的第 i 个元素)
三、访问数组元素的两种方案
引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。
1) 使用下标
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。
2) 使用指针
也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。
不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量,它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以指向数组开头,也可以指向其他元素。(例1_4 使用指针自增访问数组元素)(不能使用数组名自增访问)
数组指针
定义:如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:
dataType *arrayName[length];
[ ]的优先级高于*,该定义形式应该理解为:
dataType *(arrayName[length]);
括号里面说明arrayName是一个数组,包含了length个元素,括号外面说明每个元素的类型为dataType *即一个存放dataType类型数据的地址。
(例2_1 指针数组存放变量的地址)
(例2_2、2_3 指针数组与字符串结合)
指针与二维数组
一、指向二维数组指针的定义
二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; 假设数组 a 中第 0 个元素的地址为 1000,那么每个一维数组的首地址如下图所示:
为了更好的理解指针和二维数组的关系,我们先来定义一个指向 a 的指针变量 p:
int (*p)[4] = a; //int (*p)[4]; p = a;
// int arr[] = {1,2,3}; int *p = arr;
// int arr[][4] = {{1,2},{3,4},{5,6}}; int (*p)[4] = arr;
注意:1、括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。
2、[ ]的优先级高于*,( )是必须要加的,如果赤裸裸地写作int *p[4],那么应该理解为int *(p[4]),p 就成了一个指针数组,而不是二维数组指针。
二、用指向二维数组的指针访问二维数组
1) p指向数组 a 的开头,也即第 0 行;p+1前进一行,指向第 1 行。
2) *(p+1) = a[1]表示取地址上的数据,也就是整个第 1 行数据。(例3_1)
3) *(p+1)+1表示第 1 行第 1 个元素的地址。如何理解呢?
*(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。
4) *(*(p+1) +1) = a[1][1] 表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据。
根据上面的结论,可以很容易推出以下的等价关系:
a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
(例3_2)