C语言的高效得益于它指针功能的强大。然而C语言中的指针和数组的关系似乎很“纠结”,让人爱恨交织。指向数组的指针变量、指针数组等,似乎总是“你中有我,我中有你”。
目录
一、数组名的特殊意义及其在访问数组元素中的作用
二、指针运算的特殊性及其在访问数组元素中的作用
三、数组和指针作为函数参数进行模拟按引用调用中的相似性
一、数组名的特俗意义及其在访问数组元素中的作用
一旦给出数组的定义,编译系统就会为其在内存中分配固定的存储单元。相应地,数组的首地址也就确定了。数组元素在内存中是连续存放的,C语言中的数组名有特殊的含义,它代表存放数组元素的连续空间的首地址,即指向数组中第一个元素的指针常量。因此,数组元素既可用下标法也可用指针法来引用。
例题:例程,分别使用下标法和指针法。
下标法:
#include <stdio.h>
int main(void)
{
int a[5],i;
printf("Input five numbers:");
for(i=0;i<5;i++)
{
scanf("%d",&a[i]);
}
for(i=0;i<5;i++)
{
printf("%4d",a[i]);
}
printf("\n");
return 0;
}
假设数组a的首地址为0x0022ff40,每个元素占4个字节内存。如下图1-1所示,数组a的5个数组元素将占据从0x0022ff40到0x0022ff53的共20个字节的存储空间,元素a[0]的地址为0x0022ff40,元素a[1]的地址为0x0022ff44,以此类推,元素a[4]的地址为0x0022ff50。
因数组名a代表数组的首地址,即元素a[0]的地址(&a[0]),所以表达式a+1表示首地址后下一个元素的地址,即数组中的第2个元素即下标为1的元素a[1]的地址(&a[1])。由此可知,表达式a+i代表数组中下标为i的元素a[i]的地址(&a[i])。已知数组元素的地址后,就可以通过间接寻址来引用数组中的元素了。例如,*a或*(a+0)表示取首地址a所指的存储单元中的内容,即元素a[0],*(a+0)表示取出首地址a所指的存储单元中的内容,即元素a[0],*(a+i)表示取出首地址元素后第i个元素的内容,即下标为i的元素a[i]。
因此程序可以使用如下方法。
指针法:
#include <stdio.h>
int main(void)
{
int a[5];
int i;
for(i=0;i<5;i++)
{
scanf("%d",a+i);
}
for(i=0;i<5;i++)
{
printf("%4d",*(a+i));
}
return 0;
}
数组元素之所以能通过这种方法来引用,是因为数组的下标运算符[ ]实际上就是以指针作为其操作数的。例如,a[i]被编译器解释为表达式*(a+i),即表示引用数组首地址所指元素后面的第i个元素,而&a[i]表示取数组a的第i+1个元素的地址,它等价于指针表达式a+i。
二、指针运算的特殊性及其在访问数组元素中的作用
指针的算数运算和关系运算常常是针对数组元素而言的。因数组在内存中是连续存放的,所以指向同一数组中不同元素的两个指针的关系运算常用于比较它们所指元素在数组中的前后位置关系。指针的算数运算(如增1和减1)则常用于移动指针的指向,使其指向数组中的其他元素。当然,仅当运算结果仍指向同一数组中的元素时,指针的算术运算才有意义。
如果定义了一个指向整型数据的指针变量p,并使其值为数组a的可访问数组a的元素,如下图所示。注意,p+1与p++本质上是两个不同的操作,因为没有对p进行赋值操作,所以p+1并不改变当前指针的指向,p仍然指向原来指向的元素,而p++相当于执行了p=p+sizeof(p的基类型),因此p执行了赋值操作而改变了指针p的指向,此外该操作并不是将指针p向前移动了一个字节,而是将指针变量p向前移动了一个元素位置,即指向了下一个元素。
此外,p++并非将指针变量p的值简单的加1,而是加上1*sizeof(基类型)个字节。例如,如果基类型为int,而sizeof(int)为4的话,那么p++就相当于p加上了4个字节。
采用 通过移动指针变量p来引用数组元素的方法,可将上例程序进行如下修改。
#include <stdio.h>
int main(void)
{
int a[5],*p;
printf("Input five numbers:");
for(p=a;p<a+5;p++)
{
scanf("%d",p);
}
for(p=a;p<a+5;p++)
{
printf("%4d",*p);
}
printf("\n");
return 0;
}
因a被解释为一个指向a[0]的整型指针常量,所以执行第6行for语句中的赋值操作p=a后,指针变量p也指向了a[0],于时就可以通过移动指针变量p来依次应用数组a的元素了。
由于指针变量p指向数组的首地址&a[0],所以*p表示取出p所指向的内存单元中的内容,即元素a[0]的值,p+1指向当前指针所指元素的下一个元素,p+i指向当前指针下面的第i个元素,*(p+i)表示取出p+i所指向的内存单元中的内容,即元素a[i]的值。
注意,虽然a和p的值都是数组的首地址,但不能像使用指针变量p那样对数组名a执行增1减1操作。这是因为p是指针变量,可通过赋值操作改变它的值,使p指向数组中的其他元素,而数组名a是指针常量,代表一个地址常量,其值是不能被改变的。
指针也可用下标形式表示。采用指针的下标表示法:
#include <stdio.h>
int main(void)
{
int a[5],*p=NULL,i;
printf("Input five numbers:");
p=a;
for(i=0;i<5;i++)
{
scanf("%d",&p[i]);
}
p=a;
for(i=0;i<5;i++)
{
printf("%4d",p[i]);
}
printf("\n");
for(i=0;i<5;i++)
{
printf("%4d",a[i]);
}
printf("\n");
return 0;
}
可见,用数组实现的操作也可用指针来实现。由于增1运算的执行效率很高,所以利用指针增1运算实现指针的移动,省去了每寻址一个数组元素都要进行的指针算数运算。因此,在上面的4中方法中,第2中方法的执行效率最高,而其他计中方法的执行效率是一样的。虽然用指针编程比数组编程效率高,但使用数组编程的方法更加直观和容易理解。
三、数组和指针作为函数参数进行模拟按引用调用中的相似性
用数组名和用指向一维数组的指针变量作函数实参,向被调函数传递的都是数组的起始地址,都是模拟按引用调用。一维数组作函数形参时,因为它只起到接收数组的起始地址的作用,所以会发生数组类型到指针类型的隐式转换,即使将形参声明为一维数组,它也将退化为指针,系统仅仅为其分配指针所占的内存空间,并不为形参数组分配额外的存储空间,而是让形参数组共享实参数组所占的存储空间。因此用一维数组作函数形参与用指针变量作函数形参本质上是一样的,因为它们接收的都是数组的起始地址,都需要按此地址对主调函数中的实参数组元素进行间接寻址,因此在被调函数中既能以下标的形式也能以指针的形式来访问数组元素。需要注意的是,数组和指针并非在所有情况下都是等同的。例如,sizeof(数组名)和sizeof(指针变量名)就是不可互换的。
例题:演示数组和指针变量作为函数参数
方法1:被调函数的形参声明为数组类型,用下标法访问数组元素。
void InputArray(int a[],int n);
void OutputArray(int a[],int n);
void InputArray(int a[],int n)
{
int i;
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
}
void OutputArray(int a[],int n)
{
int i;
for(i=0;i<;i++)
{
printf("%4d",a[i]);
}
printf("\n");
}
方法2:被调函数的形参声明为指针变量,用指针法访问数组元素。
void InputArray(int *pa,int n);
void OutputArray(int *pa,int n);
void InputArray(int *pa,int n)
{
int i;
for(i=0;i<n;i++,pa++)
{
scanf("%d",pa);
}
}
void OutputArray(int *pa,int n)
{
int i;
for(i=0;i<n;i++,pa++)
{
printf("%4d",*pa);
}
printf("\n");
}
方法3:被调函数的形参声明为数组类型,用指针法访问数组元素。
void InputArray(int a[],int n);
void OutputArary(int a[],int n);
void InputArray(int a[],int n)
{
int i;
for(i=0;i<n;i++)
{
scanf("%d",a+i);
}
}
void OutputArray(int a[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%4d",*(a+i));
}
printf("\n");
}
方法4:被调函数的形参声明为指针变量,用下标法访问数组元素。
void InputArray(int *pa,int n);
void OutputArray(int *pa,int n);
void InputArray(int *pa,int n)
{
int i;
for(i=0;i<n;i++)
{
scanf("%d",&pa[i]);
}
}
void OutputArray(int *pa,int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%d",pa[i]);
}
printf("\n");
}
在主函数中,都可以用数组名作为函数实参。
#include <stdio.h>
int main(void)
{
int a[5];
printf("Input five numbers:");
InputArray(a,5);
OutputArray(a,5);
return 0;
}
//也可以用指针变量作为函数参数。
int main(void)
{
int a[5];
int *p=a;
printf("Input five numbers:");
InputArray(p,5);
OutputArray(p,5);
return 0;
}