对于数组int a[5],a[0]表示的是数组中的第1个元素,那a指的又是什么呢?由于数组名表示数组从什么地方开始存放。因此,数组名a表示的是一个地址。指针存放的也是地址值,那么数组名与指针之间似乎存在某种相似性。
1、一维数组与指针
例1. 初识数组和指针 - 单个字符的输出与多个字符的输入
int main()
{
char single;
char multiple[10];
scanf("%c", &single);
fflush(stdin); //清除键盘缓冲区,准备下一次输入
scanf("%s", multiple);
printf("single: %c \n", single);
printf("multiple: %s \n", multiple);
return 0;
}
例2. 使用数组名和指针分别访问数组中的元素
int main()
{
int pos[5] = { 1, 3, 5, 7, 9 };
int *p = pos;
// &pos[0], pos, p三者都是数组第1个元素的首地址
for(int i=0; i<5; i++)
printf("%d %d %d %d \n", pos[i], /* 元素访问方式:数组名+索引 */
/* 元素访问方式:首地址+偏移量 */
*(&pos[0] + i),
*(pos + i),
*(p + i)
);
return 0;
}
运行结果:
1 1 1 1
3 3 3 3
5 5 5 5
7 7 7 7
9 9 9 9
Press any key to continue
分析:数组名pos表示的是数组中元素从什么地方开始存储,它实际上是一个常量指针,它指向数组中第一个元素的地址。它的类型取决于数组元素的类型。在本实例中,数组元素的类型为int类型,那pos的类型是“指向int的常量指针”。这样我们可以声明一个与pos同类型的变量p,然后将pos赋给该变量p。因此p的声明方法必须是:int *p;
例3. 数组名作为函数参数传递 - 形参的类型必须与实参一致
/* 函数声明时,变量名,数组名,指针名可省略 */
void print_v1(int [], int);
void print_v2(int *, int);
int main()
{
int pos[5] = { 1, 3, 5, 7, 9 };
/* 输出数组元素以及各元素地址 */
for(int i=0; i<5; i++)
printf("%p pos[%d]:%d *(pos+%d):%d\n", pos+i, i, i, pos[i], *(pos+i) );
print_v1(pos, 5); //调用形参为版本1的函数
print_v2(pos, 5); //调用形参为版本2的函数
return 0;
}
void print_v1(int x[], int count) //形参为数组
{
printf("\n");
/* 输出数组元素以及各元素地址 */
for(int i=0; i<count; i++)
printf("%p x[%d]:%d *(x+%d):%d\n", x+i, i, i, x[i], *(x+i) );
}
void print_v2(int *p, int count) //形参为指针
{
printf("\n");
/* 输出数组元素以及各元素地址 */
for(int i=0; i<count; i++)
printf("%p p[%d]:%d *(p+%d):%d\n", p+i, i, i, p[i], *(p+i) );
}
输出结果:
0012FF6C pos[0]:0 *(pos+1):1
0012FF70 pos[1]:1 *(pos+3):3
0012FF74 pos[2]:2 *(pos+5):5
0012FF78 pos[3]:3 *(pos+7):7
0012FF7C pos[4]:4 *(pos+9):9
0012FF6C x[0]:0 *(x+1):1
0012FF70 x[1]:1 *(x+3):3
0012FF74 x[2]:2 *(x+5):5
0012FF78 x[3]:3 *(x+7):7
0012FF7C x[4]:4 *(x+9):9
0012FF6C p[0]:0 *(p+1):1
0012FF70 p[1]:1 *(p+3):3
0012FF74 p[2]:2 *(p+5):5
0012FF78 p[3]:3 *(p+7):7
0012FF7C p[4]:4 *(p+9):9
Press any key to continue
分析:该程序中的三种输出都是针对同一地址进行。传递的是数组名pos,形参的声明必须和pos的类型相同。pos为一个“指向int型的指针”,因此形参也必须是一个“指向int型的指针”。这样可以将形参声明为一维int类型的数组,也可以声明为一个指向int型的指针int *。
2、二维数组与指针
与一维数组相比,二维数组名与指针之间的差异更大一些。为了理解二维数组与指针之间的对应关系,往往把二维数组看成一维数组,该一维数组中的元素也是一元数组。如:二维数组int pos[2][3]可以理解为一维数组int3 pos[2],int3是我们为方便理解而定义的一种新数据类型,一个int3包含3个int型元素。
例1. 二维数组的输出
int main()
{
int pos[2][3] = { 1, 3, 5, 7, '9' };
printf("元素访问方式:数组名+索引:pos[i][j] \n");
for(int i=0; i<2; i++)
for(int j=0; j<3; j++)
printf("pos[%d][%d]: %p %d \n", i, j, &pos[i][j], pos[i][j]);
printf("\n");
printf("元素访问方式:首地址+元素偏移地址:*(&pos[0][0]+3*i+j) \n");
for(i=0; i<2; i++)
for(int j=0; j<3; j++)
printf("pos[%d][%d]: %p %d \n", i, j, &pos[i][j], *(&pos[0][0]+3*i+j));
return 0;
}
程序输出:
元素访问方式:数组名+索引:pos[i][j]
pos[0][0]: 0012FF68 1
pos[0][1]: 0012FF6C 3
pos[0][2]: 0012FF70 5
pos[1][0]: 0012FF74 7
pos[1][1]: 0012FF78 57
pos[1][2]: 0012FF7C 0
元素访问方式:首地址+元素偏移地址:*(&pos[0][0]+3*i+j)
pos[0][0]: 0012FF68 1
pos[0][1]: 0012FF6C 3
pos[0][2]: 0012FF70 5
pos[1][0]: 0012FF74 7
pos[1][1]: 0012FF78 57
pos[1][2]: 0012FF7C 0
Press any key to continue
例2. 二维数组的地址情况
int main()
{
int boo[3] = { 10, 20, 30 };
int pos[3][4] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 };
printf("元素地址(方法一): \n");
printf("&pos[0][0~3]: %p %p %p %p\n", &pos[0][0], &pos[0][1], &pos[0][2], &pos[0][3]);
printf("&pos[1][0~3]: %p %p %p %p\n", &pos[1][0], &pos[1][1], &pos[1][2], &pos[1][3]);
printf("&pos[2][0~3]: %p %p %p %p\n", &pos[2][0], &pos[2][1], &pos[2][2], &pos[2][3]);
/* 三种方法获取二维数组中各行的首地址 */
printf("行地址(方法一): \n");
printf("pos+0: %p \n", pos+0);
printf("pos+1: %p \n", pos+1);
printf("pos+2: %p \n", pos+2);
printf("行地址(方法二): \n");
printf("*(pos+0): %p \n", *(pos+0));
printf("*(pos+1): %p \n", *(pos+1));
printf("*(pos+2): %p \n", *(pos+2));
printf("行地址(方法三): \n");
printf("pos[0]: %p \n", pos[0]);
printf("pos[1]: %p \n", pos[1]);
printf("pos[2]: %p \n", pos[2]);
/* 对比:对一维数组进行的相同操作 */
printf("一维数组: \n");
printf("*(boo+0): %d \n", *(boo+0));
printf("*(boo+1): %d \n", *(boo+1));
printf("*(boo+2): %d \n", *(boo+2));
printf("boo[0]: %d \n", boo[0]);
printf("boo[1]: %d \n", boo[1]);
printf("boo[2]: %d \n", boo[2]);
printf("元素地址(方法二): \n");
printf("pos[0]+0~3: %p %p %p %p\n", pos[0], pos[0]+1, pos[0]+2, pos[0]+3);
printf("pos[1]+0~3: %p %p %p %p\n", pos[1], pos[1]+1, pos[1]+2, pos[1]+3);
printf("pos[2]+0~3: %p %p %p %p\n", pos[2], pos[2]+1, pos[2]+2, pos[2]+3);
printf("元素地址(方法三): \n");
printf("*(pos+0)+0~3: %p %p %p %p\n", *(pos+0), *(pos+0)+1, *(pos+0)+2, *(pos+0)+3);
printf("*(pos+1)+0~3: %p %p %p %p\n", *(pos+1), *(pos+1)+1, *(pos+1)+2, *(pos+1)+3);
printf("*(pos+2)+0~3: %p %p %p %p\n", *(pos+2), *(pos+2)+1, *(pos+2)+2, *(pos+2)+3);
return 0;
}
运行结果:
元素地址(方法一):
&pos[0][0~3]: 0012FF44 0012FF48 0012FF4C 0012FF50
&pos[1][0~3]: 0012FF54 0012FF58 0012FF5C 0012FF60
&pos[2][0~3]: 0012FF64 0012FF68 0012FF6C 0012FF70
行地址(方法一):
pos+0: 0012FF44
pos+1: 0012FF54
pos+2: 0012FF64
行地址(方法二):
*(pos+0): 0012FF44
*(pos+1): 0012FF54
*(pos+2): 0012FF64
行地址(方法三):
pos[0]: 0012FF44
pos[1]: 0012FF54
pos[2]: 0012FF64
一维数组:
*(boo+0): 10
*(boo+1): 20
*(boo+2): 30
boo[0]: 10
boo[1]: 20
boo[2]: 30
元素地址(方法二):
pos[0]+0~3: 0012FF44 0012FF48 0012FF4C 0012FF50
pos[1]+0~3: 0012FF54 0012FF58 0012FF5C 0012FF60
pos[2]+0~3: 0012FF64 0012FF68 0012FF6C 0012FF70
元素地址(方法三):
*(pos+0)+0~3: 0012FF44 0012FF48 0012FF4C 0012FF50
*(pos+1)+0~3: 0012FF54 0012FF58 0012FF5C 0012FF60
*(pos+2)+0~3: 0012FF64 0012FF68 0012FF6C 0012FF70
Press any key to continue
有了例2中的多种地址获取方式,就很容易通过这些方式获得数组中的元素了。注意:p[i] 与 *(p+i) 是等价的。
例3. 在例2的基本上使用解除引用操作符*输出二维数组中的元素
int main()
{
int pos[3][4] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 };
int i, j;
printf("数组的输出(方法一): \n");
for(i=0; i<3; i++)
{
for(j=0; j<4; j++)
printf("pos[i][j]: %d\n", pos[i][j]);
printf("\n");
}
printf("行首元素的输出(方法一): \n");
for(i=0; i<3; i++)
printf("**(pos+i): %d \n", **(pos+i));
printf("行首元素的输出(方法二): \n");
for(i=0; i<3; i++)
printf("*pos[i]: %d \n", *pos[i]);
printf("数组的输出(方法二): \n");
for(i=0; i<3; i++)
for(j=0; j<4; j++)
printf("*(pos[i]+j): %d\n", *(pos[i]+j));
printf("数组的输出(方法三): \n");
for(i=0; i<3; i++)
for(j=0; j<4; j++)
printf("*(*(pos+i)+j): %d\n", *(*(pos+i)+j));
return 0;
}
程序输出:
数组的输出(方法一):
pos[i][j]: 1
pos[i][j]: 3
pos[i][j]: 5
pos[i][j]: 7
pos[i][j]: 9
pos[i][j]: 11
pos[i][j]: 13
pos[i][j]: 15
pos[i][j]: 17
pos[i][j]: 19
pos[i][j]: 21
pos[i][j]: 23
行首元素的输出(方法一):
**(pos+i): 1
**(pos+i): 9
**(pos+i): 17
行首元素的输出(方法二):
*pos[i]: 1
*pos[i]: 9
*pos[i]: 17
数组的输出(方法二):
*(pos[i]+j): 1
*(pos[i]+j): 3
*(pos[i]+j): 5
*(pos[i]+j): 7
*(pos[i]+j): 9
*(pos[i]+j): 11
*(pos[i]+j): 13
*(pos[i]+j): 15
*(pos[i]+j): 17
*(pos[i]+j): 19
*(pos[i]+j): 21
*(pos[i]+j): 23
数组的输出(方法三):
*(*(pos+i)+j): 1
*(*(pos+i)+j): 3
*(*(pos+i)+j): 5
*(*(pos+i)+j): 7
*(*(pos+i)+j): 9
*(*(pos+i)+j): 11
*(*(pos+i)+j): 13
*(*(pos+i)+j): 15
*(*(pos+i)+j): 17
*(*(pos+i)+j): 19
*(*(pos+i)+j): 21
*(*(pos+i)+j): 23
Press any key to continue
例4:改用指向数组的指针输出数组元素
指向数组的指针,或数组指针是多维数组中的一个概念。对于一维数组的情况往往是:
int pos[10];
int *p = pos;
如果要声明一个指向二维数组的指针,该怎么办?这时候要用到数组指针。
我们知道,数组名是一个地址。对于一维数组pos[10]而言,对数组名pos使用*号解除引用后可直接得到该数组的第一个元素,如:int b = *pos;对pos+1解除引用后得到该数组的第二个元素,如可以这样操作:int c = *(pos+1); 这些元素的类型为int型。因此,声明的p指针也必须是int型,从而保持类型的一致性。
假设有二维数组int pos[3][4]; 应该怎样声明一个指向数组名pos的指针呢?对数组名pos使用*号解除引用后实际上是数组第一行元素的首地址,该行是一个包含4个int元素的数组。因此,声明的指针其类型必须也是一个指向包含4个int元素的类型。
所以声明方法为:
int pos[3][4];
int (*p)[4] = pos;
以下实例声明一个指向二维数组名的数组指针,其中的sizeof操作符更有助于理解int (*p)[4]的含义。
int main()
{
int pos[3][4] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 };
int i, j;
int (*p)[4] = pos;
printf("sizeof(pos): %d bytes \n", sizeof(pos));
printf("sizeof(pos[0]): %d bytes \n", sizeof(pos[0]));
printf("sizeof(pos[1]): %d bytes \n", sizeof(pos[1]));
printf("sizeof(pos[2]): %d bytes \n", sizeof(pos[2]));
printf("sizeof(pos[0][0]): %d bytes \n", sizeof(pos[0][0]));
printf("sizeof(pos[0][1]): %d bytes \n", sizeof(pos[0][1]));
printf("sizeof(pos[0][2]): %d bytes \n", sizeof(pos[0][2]));
printf("sizeof(p): %d bytes \n", sizeof(p));
printf("数组的输出(方法一): \n");
for(i=0; i<3; i++)
{
for(j=0; j<4; j++)
printf("p[i][j]: %d\n", p[i][j]);
printf("\n");
}
printf("行首元素的输出(方法一): \n");
for(i=0; i<3; i++)
printf("**(p+i): %d \n", **(p+i));
printf("行首元素的输出(方法二): \n");
for(i=0; i<3; i++)
printf("*p[i]: %d \n", *p[i]);
printf("数组的输出(方法二): \n");
for(i=0; i<3; i++)
for(j=0; j<4; j++)
printf("*(p[i]+j): %d\n", *(p[i]+j));
printf("数组的输出(方法三): \n");
for(i=0; i<3; i++)
for(j=0; j<4; j++)
printf("*(*(p+i)+j): %d\n", *(*(p+i)+j));
return 0;
}
程序输出:
sizeof(pos): 48 bytes
sizeof(pos[0]): 16 bytes
sizeof(pos[1]): 16 bytes
sizeof(pos[2]): 16 bytes
sizeof(pos[0][0]): 4 bytes
sizeof(pos[0][1]): 4 bytes
sizeof(pos[0][2]): 4 bytes
sizeof(p): 4 bytes
数组的输出(方法一):
p[i][j]: 1
p[i][j]: 3
p[i][j]: 5
p[i][j]: 7
p[i][j]: 9
p[i][j]: 11
p[i][j]: 13
p[i][j]: 15
p[i][j]: 17
p[i][j]: 19
p[i][j]: 21
p[i][j]: 23
行首元素的输出(方法一):
**(p+i): 1
**(p+i): 9
**(p+i): 17
行首元素的输出(方法二):
*p[i]: 1
*p[i]: 9
*p[i]: 17
数组的输出(方法二):
*(p[i]+j): 1
*(p[i]+j): 3
*(p[i]+j): 5
*(p[i]+j): 7
*(p[i]+j): 9
*(p[i]+j): 11
*(p[i]+j): 13
*(p[i]+j): 15
*(p[i]+j): 17
*(p[i]+j): 19
*(p[i]+j): 21
*(p[i]+j): 23
数组的输出(方法三):
*(*(p+i)+j): 1
*(*(p+i)+j): 3
*(*(p+i)+j): 5
*(*(p+i)+j): 7
*(*(p+i)+j): 9
*(*(p+i)+j): 11
*(*(p+i)+j): 13
*(*(p+i)+j): 15
*(*(p+i)+j): 17
*(*(p+i)+j): 19
*(*(p+i)+j): 21
*(*(p+i)+j): 23
Press any key to continue
例5. 二维数组作函数参数
void print_v1(int p[][4]);
void print_v2(int (*p)[4]);
//void print_v1(int [][4]);
//void print_v2(int (*)[4]);
int main()
{
int pos[3][4] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 };
print_v1(pos);
print_v2(pos);
return 0;
}
void print_v1(int p[][4])
{
printf("行首元素的输出: \n");
for(int i=0; i<3; i++)
printf("**(p+i): %d \n", **(p+i));
printf("数组的输出: \n");
for(i=0; i<3; i++)
for(int j=0; j<4; j++)
printf("p[i][j]: %d\n", p[i][j]);
}
void print_v2(int (*p)[4])
{
printf("数组的输出: \n");
for(int i=0; i<3; i++)
for(int j=0; j<4; j++)
printf("*(*(p+i)+j): %d\n", *(*(p+i)+j));
}
注意:函数声明时,参数列表中的数组名,变量名等可以省略。形参可以为一个省略最左边维数的二维数组,也可以是一个数组指针。
3、解引用方法输出三维数组
弄懂了二维数组的解引用方法输出,三维数组就很容易了。
例1. 三维数组的输出
void main()
{
int a[2][3][4]={1,2,3,4,5,6,7,8,9,10,11,12,\
13,14,15,16,17,18,19,20,21,22,23,24};
for(int i=0;i<2;i++)
for(int j=0;j<3;j++)
for(int k=0;k<4;k++)
printf("a[%d][%d][%d]: %d: %d \n", i, j, k, &a[i][j][k], a[i][j][k]);
printf("\na:%d \t ***a: %d \t **a: %d\n", a, ***a, **a);
printf("a[0]:%d \t **a[0]: %d \n", a[0], **a[0]);
printf("a[1]:%d \t **a[1]: %d \n", a[1], **a[1]);
printf("a[0][1]:%d \t *a[0][1]: %d \n", a[0][1], *a[0][1]);
}
运行结果:
a[0][0][0]: 1244960: 1
a[0][0][1]: 1244964: 2
a[0][0][2]: 1244968: 3
a[0][0][3]: 1244972: 4
a[0][1][0]: 1244976: 5
...
a[0][1][3]: 1244988: 8
a[0][2][0]: 1244992: 9
...
a[0][2][3]: 1245004: 12
a[1][0][0]: 1245008: 13
a[1][0][1]: 1245012: 14
a[1][0][2]: 1245016: 15
a[1][0][3]: 1245020: 16
a[1][1][0]: 1245024: 17
...
a[1][1][3]: 1245036: 20
a[1][2][0]: 1245040: 21
...
a[1][2][3]: 1245052: 24
a:1244960 ***a: 1 **a: 1244960
a
a[0]:1244960 *a[0]: 1
a[1]:1245008 *a[1]: 13
a[0][1]:1244976 *a[0][1]: 5
4、应用举例
例1. 删除二维字符数组中所以长度超过k的字符串,返回所剩字符串个数
#include <string.h>
#define N 6 //二维数组中的字符串个数
#define M 10 //二维数组中字符串的最大长度
int func(char (*a)[M], int k) //形参为指向字符串数组指针
{
int i, j=0, len;
for(i=0; i<N; i++)
{
len=strlen(a[i]);
if(len<=k)
strcpy(a[j++], a[i]); //字符串复制函数
}
return j;
}
main()
{
char fruit[N][M]={"Pear", "Banana", "Apple", "Orange", "Grape", "Lemon"}; //二维字符数组
int i, f;
printf("The original string: \n");
for(i=0; i<N; i++)
puts(fruit[i]); //puts库函数输出字符串,原型为:int __cdecl puts(const char *);
printf("\n");
f=func(fruit, 5); //实参为数组名
printf("The string which length is less than or equal to 5: \n");
for(i=0; i<f; i++)
puts(fruit[i]); //puts库函数输出字符串,原型为:int __cdecl puts(const char *);
printf("\n");
return 0;
}
运行结果:
The original string:
Pear
Banana
Apple
Orange
Grape
Lemon
The string which length is less than or equal to 5:
Pear
Apple
Grape
Lemon
总结:当我们声明一个指向二维数组的指针后,我们可以跟使用二维数组一样,使用该指针对数组进行相应操作。如上例如示:主函数中声明了一个字符型的二维数组char fruit[N][M];被调用函数参数为一个指向该数组的数组指针 char (*a)[M];当该指针指向了fruit数组,我们就可以使用puts(a[i]),它与puts(fruit[i])一样,都是输出数组中的第i个字符串,或者说二维数组的第i行元素。在这里a[i]和fruit[i]是一个指向字符串的指针,它与puts(const char *)函数的参数类型const char *是一致的,所以可以调用puts()函数输出相应的字符串。同理,可以将a[j++], a[i]作为参数字符串复制函数strcpy(char * dest, const char * src),因为strcpy函数的参数都是指向字符串的指针,与a[j++]和a[i]的类型是一致的。