C语言初学之指针的应用–Chapter 9
引言
- C语言中规定数组名代表数组的起始地址,一个数组包含若干个元素,每个数组元素都在内存中占用存储空间,都有相应的地址,指针变量可以指向数组或数组元素。
- 引用数组元素可以用下标的方法
(如a[2])
,也可以用指针的方法,也可以使用指针的方法能使目标程序内存开销少,运行速度高。
9.1. 一维数组与指针
9.1.1. 数组名的特殊意义及其在访问数组元素中的作用
-
一旦给出数组的定义,系统就会为其在内存中分配固定的存储单元。相应地,数组的首地址也就确定了。
-
数组元素在内存中是连续存放的,C语言中数组名的特殊含义就是它代表存放数组元素的连续的存储空间的首地址,即指向数组中的第一个元素的常量指针。
-
因此数组元素既可以用下标法来引用,也可以用指针法来引用。
- 例9.1 下面的程序用于演示数组元素的引用方法。 先使用
下标法
编写数组元素的输入和输出的程序:
- 例9.1 下面的程序用于演示数组元素的引用方法。 先使用
#include<stdio.h>
int main(){
int a[5],i;
for ( i = 0; i < 5; i++)
{
scanf("%d",&a[i]);
}
for( i = 0; i < 5; i++)
{
printf("%3d\n",a[i]);
}
printf("\n");
return 0;
}
- 由于数组名代表数组的首地址,即a[0]元素的地址(&a[0]),所以表达式a+1表示在当前首地址的情况下后偏移一个基类型存储空间的地址。由此可得
a[1]==a+1
、a[i]=a+i
,*(a+i)
就是地址a+i
空间存放的内容a[i]
,以下一维数组的地址示意图:
- 因此,此程序也可以写成如下的地址加偏移的等价形式:
#include<stdio.h>
int main(){
int a[5],i;
for ( i = 0; i < 5; i++)
{
scanf("%d",a+i);
}
for( i = 0; i < 5; i++)
{
printf("%3d\n",*(a+i));
}
printf("\n");
return 0;
}
- 数组元素之所以可以通过这种方法来引用,是因为数组的下标运算符[]实际上就是以指针作为其操作数的。
9.1.2. 数组元素的指针
- 定义一个指向数组元素的指针变量:
int a[5],*p;
- 下面是对该指针变量进行赋值:
p=&a[0];
- C语言规定数组名代表数组的首元素的地址。因此有:
p=&a[0];
等价于p=a;
,其作用是将指针变量p指向数组元素的首地址,其指向关系如图所示:
9.1.3. 指针变量的运算
- 指针变量的运算是指变量所持有的地址为运算对象进行的运算,所以指针变量的运算实际上就是地址的运算。指针的运算常常是针对数组元素的。
- 指针加上整数(
同一数组中的元素
)
指针p加上整数j将产生指向特定元素的指针,这个特定元素是p原来指向元素后的第j个位置.
定义数组和指针且如下图所示:
int a[10],*p,*q;
p=&a[2];
指针指向如图:
q=p+3;
表示在q当前的位置上向后偏移3个元素位置,如图:
p+=6;
表示在p当前指向的位置上向后偏移6个元素的位置,如图:
- 指令减去整数
指针p减去整数j将产生特定元素的指针,这个特定元素是p原来指向的元素前的第j个位置。
int a[10],*p,*q;
p=&a[8];
此时指针指向如图:
q=p-3;
q指针指向位置如图:
p-=6;
p指针指向位置如图:
- 两个指针相减
当两个指针相减的时候,结果为两个指针之间的距离,用数组元素的个数来表示。如果p指向a[i]且q指向a[j],则p-q就等于i-j。
int a[10],i,*p,*q;
p=&a[5];
q=&a[1];
i=p-q;
指针位置如图所示:
- 指针的比较
可以用关系运算符(<、<=、>、>=)和判断运算符(==和!=)进行指针的比较运算,只有在两个指针指向同一数组时,才有意义。
例如:
int a[10],*p,*q;
p=&a[5];
q=&a[1];
//p<=q的结果为0,而p>=q的结果为1
- 用指针变量访问数组元素
假设有如下定义:
int a[5],*p;
p=a;
表明了定义了一个指向整形数据的指针变量p,使其指向了数组a的首地址,通过这个指针变量p就可以访问数组a的元素。
注意:指针运算p+1和p++单看表面现象,都是对当前的指针p进行加1运算,但是p+1并不改变当前指针的指向,p++则使p的指向从当前位置指向下一个元素。
此外,p++的偏移问题,是偏移1*sizeof(基类型)个字节。
采用移动指针变量p来引用数组元素的方法,可将例9.1程序修改如下:
#include<stdio.h> int main(){ int a[5],*p; for (p=a;p<a+5;p++) { scanf("%d",p); } for(p=a;p<a+5;p++) { printf("%3d\n",*p); } printf("\n"); return 0; }
- 对指针与数组元素的关系归纳如下
- &a[j],a+j,p+j等价,代表a数组第j+1个元素的地址。
- a[j],﹡(a+j),p[j],﹡(p+j)等价,代表a数组第j+1个元素的值。
- ﹡(p++)等价于a[j++]、﹡(p- -)等价于a[j- -]、﹡(++p)等价于a[++j]、﹡(- -p)等价于a[- -j]。
- 指针也可以用下标的形式表示。采用指针的下标表示法,修改例9.1程序如下:
#include<stdio.h>
int main(){
int a[5],i,*p;
p=a;
for (i=0;i<=5;i++)
{
scanf("%d",&p[i]);
}
for(i=0;i<=5;i++)
{
printf("%3d\n",p[i]);
}
printf("\n");
return 0;
}
- 例9.2分析下面程序运行的结果
#include<stdio.h>
int main(void)
{
int a[]={5,15,25,35,45,55,65,75,85,95},m,*p;
p=a;
m=*p++; //e1行
printf("m=%d\n",m);
m=*++p; //e2
printf("m=%d\n",m);
m=++(*p); //e3
printf("m=%d\n",m);
m=(*p)++; //e4
printf("m=%d\n",m);
return 0;
}
- 在上述程序中,执行e1行时,先取出p所指向的地址,即a[0]=5,赋值给m后,再执行++运算,使p指向下一个元素a[1]。
执行e2行时,p先执行++运算,使p指向下一个元素a[2],取出p所指向的元素的值,即a[2]=25,再赋值给m。
执行e3时,先取出p所指向的值,即a[2]=25,再执行++运算将a[2]的值加1,即a[2]的值为26,最后再赋值给m。
执行e4时,先取出p所知的值a[2]=26赋值给m后,再执行++运算,即此时m的值为26。
所以最终的结果如下:
m=5
m=25
m=26
m=26
- 使用指针变量时应注意以下几个问题:
① 不要使用没有赋值的指针变量,使用指针之前一定要对它进行正确的赋值
② 使用指针变量访问数组元素时,要随时检查指针的变化范围和指针当前的值,使指针的指向不能超过数组的上下界。
③ 无论传递数组首地址的函数参数用数组名还是指针,其实质都是指针,在函数被调用时,该指针通过参数传递均指向数组。 - 下面来两个实例:
例9.3演示数组和指针变量作为函数的参数,实现的功能是输入几个学成的成绩,求其平均成绩。
#include<stdio.h>
#define N 5
/* 方法一:实参用指针变量,形参用数组名 */
float aver(float p[]) //定义aver()函数用于计算平均成绩,形参用数组名
{
int i;
float av,s=0;
for(i=0;i<N;i++)
{
s+=p[i]; //利用下标法取数组元素的值
}
av=s/N;
return(av);
}
int main(void)
{
float score[N],av,*p=score;
int i;
printf("请输入5个学生的成绩!");
for(i=0;i<N;i++)
{
scanf("%f",&score[i]);
}
av=aver(p); //调用aver()函数时,实参用指针变量
printf("这5个学生的平均成绩是%5.2f",av);
return 0;
}
/* 方法二:实参用数组名,形参用指针变量 */
float aver(float *pa) //定义aver()函数用于计算平均成绩,形参用指针变量
{
int i;
float av,s=0;
for(i=0;i<N;i++)
{
s+=*pa++; //利用指针变量取数组元素的值
}
av=s/N;
return(av);
}
int main()
{
float score[N],av;
int i;
printf("请输入5个学生的成绩");
for(i=0;i<N;i++)
{
scanf("%f",&score[i]);
}
av=aver(score); //调用aver()函数时,实参用数组名
printf("这5个学生的平均成绩是%5.2f",av);
return 0;
}
/* 方法三:实参用指针变量,形参也用指针变量。 */
float aver(float *pa)
{
float av,s=0;
int i;
for(i=0;i<N;i++)
{
s+= *pa++;
}
av=s/N;
return(av);
}
int main()
{
float score[N],*p=score,av;
int i;
printf("请输入5个学生的成绩!");
for(i=0;i<N;i++)
{
scanf("%f",&score[i]);
}
av = aver(p);
printf("average=%5.2f",av);
return 0;
}
例9.4输入n个数,对其中的正数统计个数并求和,程序最后输出原始数据和统计结果。
#include<stdio.h>
#define N 5
int main()
{
int a[N],*p;
int count=0,sum=0;
printf("%d!",N);
for(p=a;p<a+N;p++)
{
scanf("%d",p);
if (*p>0)
{
sum+= *p;
count++;
}
}
p=a;
while (p<a+N)
{
printf("%-3d",*p++);
}
printf("\n");
printf("count=%d\n",count);
printf("sum=%d\n",sum);
return 0;
}
- 当然,我们可以将冒泡排序法(从小到大的次序)的程序通过指针的方法修改如下:
#include<stdio.h>
#define M 5
void sort(int v[],int n)
{
int i,j,temp;
for(i=0;i<n;i++)
{
for(j=n-1;j>i;j--)
{
if (v[j-1]>v[j]) //v是指针变量,v[j-1],v[j]是用下标法表示指针所指
//对象的值,这里a[j-1]=*(a+j-1),a[j]=*(a+j)
{
temp=v[j-1];
v[j-1]=v[j];
v[j]=temp;
}
}
}
}
void print(int v[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%3d",v[i]);
}
printf("\n");
}
int main()
{
int i,a[M];
printf("input %d numbers:\n",M);
for(i=0;i<M;i++)
{
scanf("%d",&a[i]);
}
sort(a,M);
print(a,M);
return 0;
}
例9.5用选择法对数组元素按照从小到大顺序排序
#include<stdio.h>
int main()
{
void sort(int x[],int n);
int *p,i,a[10];
p=a;
for(i=0;i<10;i++)
{
scanf("%d",p++);
}
p=a;
sort(p,10); //对数组内的元素进行选择排序
for(p=a,i=0;i<10;i++)
{
printf("%3d",*p);
p++;
}
return 0;
}
void sort(int x[],int n)
{
int i,j,k,t;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(x[j]>x[k])
{
k=j;
}
}
if(k!=i)
{
t=x[i];
x[i]=x[k];
x[k]=t;
}
}
}