1. 指针的运算
指针变量几个问题的进一步说明 指针变量可以进行某些运算,但其运算的种类是有限的。它只能进行赋值运算和部分算术运算及关系运算。
- 指针运算符
1) 取地址运算符&:取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。在 scanf 函数及前面介绍指针变量赋值中,我们已经了解并使用了&运算符。 2) 取内容运算符*:取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。 需要注意的是指针运算符*和指针变量说明中的指针说明符*不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量值。
- 指针变量的运算
1) 赋值运算:指针变量的赋值运算有以下几种形式。 A) 指针变量初始化赋值,前面已作介绍。 B)把一个变量的地址赋予指向相同数据类型的指针变量。
例如:
int a,*pa;
pa=&a; /*把整型变量 a 的地址赋予整型指针变量 pa*/
C) 把一个指针变量的值赋予指向相同类型变量的另一个指针变量。
如:
int a,*pa=&a,*pb;
pb=pa; /*把 a 的地址赋予指针变量 pb*/
由于 pa,pb 均为指向整型变量的指针变量,因此可以相互赋值。
D) 把数组的首地址赋予指向数组的指针变量。
例如:
int a[5],*pa;
pa=a;
(数组名表示数组的首地址,故可赋予指向数组的指针变量 pa)
也可写为:
pa=&a[0]; /* 数 组 第 一 个 元 素 的 地 址 也 是 整 个 数 组 的 首 地 址 ,也可赋予 pa*/
当然也可采取初始化赋值的方法:
int a[5],*pa=a;
E) 把字符串的首地址赋予指向字符类型的指针变量。
例如:
char *pc;
pc=”C Language”;
或用初始化赋值的方法写为:
char *pc=”C Language”;
这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。在后面还将详细介绍。
F) 把函数的入口地址赋予指向函数的指针变量。
例如:
int (*pf)();
pf=f; /*f 为函数名*/
2)加减算术运算
对于指向数组的指针变量,可以加上或减去一个整数 n。设 pa 是指向数组 a 的指针变量,则 pa+n,pa-n,pa++,++pa,pa–,–pa 运算都是合法的。指针变量加或减一个整数 n 的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动 n 个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加 1 或减 1 在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加 1,即向后移动 1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加 1。例如:
int a[5],*pa;
pa=a; /*pa 指向数组 a,也是指向 a[0]*/
pa=pa+2; /*pa 指向 a[2],即 pa 的值为&pa[2]*/
指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。
3)两个指针变量之间的运算:只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。
A) 两指针变量相减:两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
例如 pf1 和 pf2 是指向同一浮点数组的两个指针变量,设 pf1 的值为 2010H,pf2的值为 2000H,而浮点数组每个元素占 4 个字节,所以 pf1-pf2 的结果为(2000H-2010H)/4=4,表示 pf1 和 pf2 之间相差 4 个元素。两个指针变量不能进行加法运算。 例如,pf1+pf2 是什么意思呢?毫无实际意义。
B) 两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。
例如:
pf1==pf2 表示 pf1 和 pf2 指向同一数组元素;
pf1>pf2 表示 pf1 处于高地址位置;
pf1
指针变量还可以与 0 比较。
设 p 为指针变量,则 p==0 表明 p 是空指针,它不指向任何变量;
p!=0 表示 p 不是空指针。
空指针是由对指针变量赋予 0 值而得到的。
例如:
#define NULL 0
int *p=NULL;
对指针变量赋 0 值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋 0 值后,则可以使用,只是它不指向具体的变量而已。
举例:
int main() { int a=10,b=20,s,t,*pa,*pb; /*说明 pa,pb 为整型指针变量*/ pa=&a; /*给指针变量 pa 赋值,pa 指向变量 a*/ pb=&b; /*给指针变量 pb 赋值,pb 指向变量 b*/ s=*pa+*pb; /*求 a+b 之和,(*pa 就是 a,*pb 就是 b)*/ t=*pa**pb; /*本行是求 a*b 之积*/ printf("a=%d\nb=%d\na+b=%d\na*b=%d\n",a,b,a+b,a*b); printf("s=%d\nt=%d\n",s,t); return 0; }
main() { int a,b,c,*pmax,*pmin; /*pmax,pmin 为整型指针变量*/ printf("input three numbers:\n"); /*输入提示*/ scanf("%d%d%d",&a,&b,&c); /*输入三个数字*/ if(a>b){ /*如果第一个数字大于第二个数字...*/ pmax=&a; /*指针变量赋值*/ pmin=&b;} /*指针变量赋值*/ else{ pmax=&b; /*指针变量赋值*/ pmin=&a;} /*指针变量赋值*/ if(c>*pmax) pmax=&c; /*判断并赋值*/ if(c<*pmin) pmin=&c; /*判断并赋值*/ printf("max=%d\nmin=%d\n",*pmax,*pmin); /*输出结果*/ }
2. 数组指针和指向数组的指针变量
一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。
指向数组元素的指针
一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。
定义一个指向数组元素的指针变量的方法,与以前介绍的指针变量相同。
例如:
int a[10]; /*定义 a 为包含 10 个整型数据的数组*/
int *p; /*定义 p 为指向整型变量的指针*/
应当注意,因为数组为 int 型,所以指针变量也应为指向 int 型的指针变量。下面是对指针变量赋值:
p=&a[0];
把 a[0]元素的地址赋给指针变量 p。也就是说,p 指向 a 数组的第 0 号元素。
C 语言规定,数组名代表数组的首地址,也就是第 0 号元素的地址。因此,下面两个语
句等价:
p=&a[0];
p=a;
在定义指针变量时可以赋给初值:
int *p=&a[0];
它等效于:
int *p;
p=&a[0];
当然定义时也可以写成:
int *p=a;
从图中我们可以看出有以下关系:
p,a,&a[0]均指向同一单元,它们是数组 a 的首地址,也是 0 号元素 a[0]的首地址。应该说明的是 p 是变量,而 a, &a[0]都是常量。在编程时应予以注意。
数组指针变量说明的一般形式为:
类型说明符 *指针变量名;
其中类型说明符表示所指数组的类型。从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。
2. 数组名作函数参数
数组名可以作函数的实参和形参。如:
main()
{int array[10];
……
……
f(array,10);
……
……
}
f(int arr[],int n);
{
……
……
}
array 为实参数组名,arr 为形参数组名。在学习指针变量之后就更容易理解这个问题了。数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。这就好象同一件物品有两个彼此不同的名称一样。
同样,指针变量的值也是地址,数组指针变量的值即为数组的首地址,当然也可作为函数的参数使用。
#include <stdio.h> float aver(float *pa); main() { float sco[5],av,*sp; int i; sp=sco; printf("\ninput 5 scores:\n"); for(i=0;i<5;i++) scanf("%f",&sco[i]); av=aver(sp); printf("average score is %5.2f",av); } float aver(float *pa) { int i; float av,s=0; for(i=0;i<5;i++) s=s+*pa++; av=s/5; return av; }
举例:
将数组 a 中的 n 个整数按相反顺序存放。
算法为:将 a[0]与 a[n-1]对换,再 a[1]与 a[n-2] 对换……,直到将 a[(n-1/2)]与a[n-int((n-1)/2)]对换。今用循环处理此问题,设两个“位置指示变量”i 和 j,i 的初值为0,j 的初值为 n-1。将 a[i]与 a[j]交换,然后使 i 的值加 1,j 的值减 1,再将 a[i]与 a[j]交换,直到 i=(n-1)/2 为止,如图所示。
#include <stdio.h> main() { int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); inv(a,10); printf("The array has benn inverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } void inv(int x[],int n) { int temp,i,j, m=(n-1)/2; for(i=0;i<=m;i++) { j=n-1-i; temp=x[i]; x[i]=x[j]; x[j]=temp; } return; }
作业: 对此程序可以作一些改动。将函数 inv 中的形参 x 改成指针变量。
#include <stdio.h> main() { int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); inv(a,10); printf("The array has benn inverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } void inv(int *x,int n) { int *p,temp,*i,*j,m=(n-1)/2; i=x; j=x+n-1; p=x+m; for(;i<=p;i++,j--) { temp=*i; *i=*j; *j=temp; } return; }
举例:
从 10 个数中找出其中最大值和最小值。
调用一个函数只能得到一个返回值,今用全局变量在函数之间“传递”数据。程序如下:
#include <stdio.h> int max,min; void max_min_value(int array[],int n) { int *p,*array_end; array_end=array+n; max=min=*array; for(p=array+1;p<array_end;p++) { if(*p>max) max=*p; else if (*p<min) min=*p; } return; } main() { int i,number[10]; printf("enter 10 integer umbers:\n"); for(i=0;i<10;i++) scanf("%d",&number[i]); max_min_value(number,10); printf("\nmax=%d,min=%d\n",max,min); }
说明:
1) 在函数 max_min_value 中求出的最大值和最小值放在 max 和 min 中。由于它们是全局,因此在主函数中可以直接使用。
2) 函数 max_min_value 中的语句:
max=min=*array;
array 是数组名,它接收从实参传来的数组 numuber 的首地址。
*array 相当于*(&array[0])。上述语句与 max=min=array[0];等价。
3) 在执行 for 循环时,p 的初值为 array+1,也就是使 p 指向 array[1]。以后每次执行 p++, 使 p 指向下一个元素。每次将*p 和 max 与 min 比较。将大者放入 max,小者放 min。
4) 函数 max_min_value 的形参 array 可以改为指针变量类型。实参也可以不用数组名,而用指针变量传递地址。
作业: 用指针作为参数重写程序
#include <stdio.h> int max,min; void max_min_value(int *array,int n) { int *p,*array_end; array_end=array+n; max=min=*array; for(p=array+1;p<array_end;p++) { if(*p>max)max=*p; else if (*p<min)min=*p; } return; } main() { int i,number[10],*p; p=number; printf("enter 10 integer umbers:\n"); for(i=0;i<10;i++,p++) scanf("%d",p); p=number; max_min_value(p,10); printf("\nmax=%d,min=%d\n",max,min); }
归纳起来,如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有以下4种:
1) 形参和实参都是数组名, a和x指的是同一组数组。
main()
{int a[10];
……
f(a,10)
……}
f(int x[],int n)
{
……
}
2) 实参数组,形参用指针变量。
main()
{int a[10];
……
f(a,10)
……}
f(int *x,int n)
{
……
}
3) 实参、型参都用指针变量。
4) 实参为指针变量,型参为数组名。
举例:用选择法对 10 个整数排序。
#include <stdio.h> main() { int *p,i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); p=a; sort(p,10); for(p=a,i=0;i<10;i++) { printf("%d ",*p);p++; } printf("\n"); } 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; } } }
说明:函数 sort 用数组名作为形参,也可改为用指针变量.