1.通过指针引用数组
(1)认识数组元素的指针
一个变量有地址,一个数组包含若干个元素,每个数组元素都在内存中占用存储单元,他们都有相同的地址。所谓数组元素的指针就是数组元素的地址。例如:
int a[10]={1,2,3,4,5,6,7,8,9,10}; //定义a为包含10个整型数据的数组
int *p; //定义p为指向整型变量的指针变量
p = &a[0]; //把a[0]元素的地址赋值给指针p
引用数组元素可以用下标法,也可以用指针法,即通过指向数组元素的指针找到所需要的元素。
C语言中,数组名代表数组中首元素的地址,所以下面的代码等价
p = &a[0]; //p的值是a【0】的地址
p = a; //p的值是数组a首元素的地址
在定义指针变量时可以对它初始化,如:
int * p = &a[0];
它等效于下面俩行:
int * p;
p = &a[0]; //不能写成*p=&a[0]
当然定义时也可以写成
int * p = a;
它的作用就是将a数组首元素的地址赋值给指针变量p
2.在引用数组元素时指针的运算
在指针已指向一个数组元素时,可以对指针进行以下运算:
加一个整数,如p+1,减一个整数,如p-1,或者p++,p--;
俩个指针相减时,如:p1 - p2,只有p1,p2指向同一数组元素时才有意义,如执行p1-p2,结果是俩个地址之差除以数组元素的长度,俩地址不能相加,没有意义。
如果指针变量p已指向数组中的一个元素,则p+1指向同一数组的下一个元素,p-1表示指向同一数组的上一个元素。
注意:执行p+1时并不是将p的值(地址)简单的加一,而是加上一个数组元素所占的字节数。
3.通过指针引用数组元素
引用数组元素时一般有俩种方法:
(1)下标法,如a【i】形式
(2)指针法,如 * (a+1)或*(p+i)其中a是数组名,p是指向数组元素的指针变量,初值为a
【例题】有一个整型数组a,有10个元素,要求输出数组中的全部元素
#include<stdio.h>
int main()
{
//下标法
int i,a[10];
for(i=0;i<10;i++){
scanf("%d",&a[i]);
}
for(i=0;i<10;i++){
printf("%d ",a[i]);
}
printf("\n");
return 0;
}
运行结果:
#include<stdio.h>
int main()
{
//通过数组名计算数组元素地址,找出数组元素的值
int i,a[10];
for(i=0;i<10;i++){
scanf("%d",&a[i]);
}
for(i=0;i<10;i++){
printf("%d ",*(a+i));
}
printf("\n");
return 0;
}
运行结果:
#include<stdio.h>
int main()
{
//通过指针变量指向数组元素
int i,a[10],*p;
for(i=0;i<10;i++){
scanf("%d",&a[i]);
}
for(p=a;p<(a+10);p++){
printf("%d ",*p);
}
printf("\n");
return 0;
}
运行结果:
注意:第三种方法中不能改为
for(p=a;a<(p+10);a++){
printf("%d",*a);
}
因为数组名a代表数组首元素的地址,它是一个指针型常量,它的值在指针运行期间是固定不变的,既然是不变的,所以a++无法实现。
要注意指针变量的当前值,例如:
【题目】通过指针变量输出整型数组a的10个元素
思路:用指针变量p指向数组元素,通过改变指针变量的值来输出
#include<stdio.h>
int main()
{
int i,a[10],*p=a;
for(i=0;i<10;i++){
scanf("%d",p++);
}
for(i=0;i<10;i++,p++){
printf("%d ",*p);
}
printf("\n");
return 0;
}
运行结果:
显然输出的值并不是数组a的各个值,问题出在指针变量p的指向,指针变量p的初始值为a的首元素,经过第一个for循环读入数据后,p已指向a数组的末尾,因此,在执行第二个for循环时,p的起始值已经变了,变成a+10,所以,程序应该改为:
#include<stdio.h>
int main()
{
int i,a[10],*p=a;
for(i=0;i<10;i++){
scanf("%d",p++);
}
p = a ; //重新使p指向a
for(i=0;i<10;i++,p++){
printf("%d ",*p);
}
printf("\n");
return 0;
}
运行结果:
指向数组元素的指针变量也可以带下标,如p【i】。当指针变量指向数组元素时,指针变量可以带下标,因为在程序编译时,对下标的处理方法是转化为地址的,p【i】处理成*(p+i)。
程序也可以改写为
for(i=0;i<10;i++){
printf("%d",*p++);
}
他们的作用完全一样,都是先输出*p的值,然后使p值加1,这样下次循环时就是下一个元素的值
*(p++)和*(++p)的作用不一样,前者是先取*p的值,再加一,后者是先加一,再取*p的值,
++(*p)表示p所指向的元素值加1,如果p=a,则++(*p)相当于++a【0】,若a【0】的值为3,在执行++(*p)后a【0】的值变为4,注意是a【0】的值加1,而不是指针p的值加1.如果当前指向数组的第i个元素a【i】,则*(p--)相当于a【i--】先对p进行“ * ”运算,然后p自减。
*(++p)相当于a【++i】,先使p自加,然后进行“ * ”运算
*(--p)相当于a【--i】,先使p自减,然后进行“ * ”运算
将++和--运算符用于指针变量十分有效,可以使指针变量向前或向后移动,指向下或向上一个数组元素,例如,想输出a数组的100个元素,可以用下面的方法:
p=a;
while(p<a+100){
printf("%d",*p++);
}
或者
p=a;
while(p<a+100){
printf("%d",*p);
p++;
}
4.用数组名作为函数参数
在前面我们知道可以用数组名作为函数的参数,例如:
#include<stdio.h>
void fun(int arr[],int n); //对fun函数声明
int main()
{
int array[100];
........
fun(array,10); //使用函数
return 0;
}
void fun(int arr[],int n){ //定义fun函数
.......
}
array是实参数组名,arr为形参数组名,当用数组名作为参数时,如果形参数组中各元素的值发生变化,实参数组元素的值也会一起变化。先看数组元素作实参时的情况,如果一个函数如下:
void swap(int x,int y);
假设函数的作用是将俩个形参(x,y)的值交换,现在有下面的函数调用:
swap(a[1],a[2]);
用数组元素a【1】,a【2】作为实参,是值传递的方式,将他们的值传递给x,y,然后交换x,y的值,但是a【1】,a【2】的值并不改变。如果使用的是数组名,实参数组名代表该数组首元素的地址,而,形参是用来接收从实参传递过来的数组首元素地址得的,因此,形参应该是一个指针变量(只有指针变量才能存放地址)。c编译都是将形参数组名作为指针变量来处理的,例如:
fun(int arr[],int n)
在程序编译时是将arr按指针变量来处理的,相当于写成:
fun(int * arr,int n)
当arr接收了实参数组首元素的地址后,arr就指向实参数组首元素,也就是指向arr【0】,*(arr+i)和arr[i]是无条件等价的,所以函数改变可以实现。
注意:实参数组名代表一个固定的地址,也就是指针常量,形参数组名不是一个固定的地址,也就是指针变量。
在函数调用进行虚实结合后,形参的值就是实参数组的首元素的地址,在函数执行时,它可以再被赋值,例如:
void fun(arr[],int n){
pritf("%d\n",*arr); //输出arr[0]的值
arr=arr+3; //形参数组名可以被赋值
printf("%d\n",*arr); //输出arr[3]的值
}
【例题】将数组a中的n个整数按相反的顺序存放。
#include<stdio.h>
void inv(int x[],int n); //对inv函数声明
int main()
{
int i,a[10]={1,2,3,4,5,6,7,8,9,10} ;
for(i=0;i<10;i++){ //输出原来数组的各个元素
printf("%d ",a[i]);
}
printf("\n");
inv(a,10); //调用函数
for(i=0;i<10;i++){
printf("%d ",a[i]); //输出交换后的数组各个元素
}
return 0;
}
void inv(int x[],int n){ //定义inv函数
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;
}
}
运行结果:
在定义inv函数时,可以不指定形参数组的大小,因为形参数组名实际上是一个指针变量。
对这个程可以做一些改动,将函数inv中形参x改成指针变量,对应的实参仍然为数组名a。
修改程序:
#include<stdio.h>
void inv(int x[],int n); //对inv函数声明
int main()
{
int i,a[10]={1,2,3,4,5,6,7,8,9,10} ;
for(i=0;i<10;i++){ //输出原来数组的各个元素
printf("%d ",a[i]);
}
printf("\n");
inv(a,10); //调用函数
for(i=0;i<10;i++){
printf("%d ",a[i]); //输出交换后的数组各个元素
}
return 0;
}
void inv(int *x,int n){ //定义inv函数
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;
}
}
运行结果:
总结: 如果有一个实参数组,想要在函数中改变此数组元素的值,实参与形参的对应关系有4种。
(1)形参和实参都用数组名,例如:
int main(){
int a[10];
......
f(a,10);
......
return 0;
}
int f(int x[],int n){
......
}
由于形参数组名x接收了实参数组首元素a[0]的地址,因此可以认为在函数调用期间,形参数组和实参数组共用一段内存单元。
(2)实参用数组名,形参用指针变量,例如:
int main(){
int a[10];
......
f(a,10);
......
return 0;
}
int f(int * x,int n){
......
}
实参a为数组名,形参x为int * 型指针变量,调用函数后,形参x指向a[0],通过x值的改变,可以指向数组a的任一元素。
(3)实参形参都用指针变量,例如:
int main(){
int a[10];
int * p = a;
......
f(p,10);
......
return 0;
}
int f(int * x,int n){
......
}
实参p和形参x都是int * 型指针变量,先使实参指针变量p指向数组a[0],然后p的值传给形参指针变量x,x的初始值也是a[0]的地址,通过x值的改变可以指向数组a的任意一个元素。
(4)实参为指针变量,形参为数组名,例如:
int main(){
int a[10];
int * p = a;
......
f(p,10);
......
return 0;
}
int f(int x[],int n){
......
}
实参p为指针变量,他指向数组a首元素,形参为数组名x,编译系统把x作为指针变量处理,现在将a[0]的地址传给形参x,使x也指向a[0],也可以理解为形参x和a数组公用同一段内存单元。
【例题】改写上题,用指针变量作实参。
#include<stdio.h>
void inv(int *x,int n); //对inv函数声明
int main()
{
int i,arr[10],* p = arr;
for(i=0;i<10;i++,p++){
scanf("%d ",p);
}
printf("\n");
p=arr;
inv(p,10);
for(;p<arr+10;p++){
printf("%d ",*p);
}
printf("\n");
return 0;
}
void inv(int *x,int n){ //定义inv函数
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;
}
}
【例题】用指针的方法对10个整数按从小到大输出
#include<stdio.h>
void sort(int x[],int n); //sort 函数声明
int main()
{
int i,*p,a[10];
p=a;
for(i=0;i<10;i++){
scanf("%d",p++); //输入10个整数
}
p=a; //指针变量p重新指向a[0]
sort(p,10); //调用sort函数
for(p=a,i=0;i<10;i++){
printf("%d ",*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;
}
}
}
}
运行结果:
5.通过指针引用多维数组
指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素,但在概念上和使用方法上会更复杂。
(1)多维数组元素的地址
以二维数组为例,假设有一个二维数组a,它有三行四列,它的定义为
int a[3][4] = { {1,3,5,7} , {9,11,13,15} , {17,19,21,23} };
a使二维数组名,数组a包含3行,即3个行元素:a[0],a[1],a[2]。而每一个行元素又是一个一维数组,它包含四个元素,即四个列元素,可以认为,二维数组是“数组的数组”。
从二维数组的角度来看,a代表二维数组首元素的地址,现在的首元素不是一个简单的整型元素,而是由四个整型元素组成的一维数组,因此a代表的是首行元素的地址,a+1代表序号为1的行的起始地址,如果二维数组首行的起始地址为2000,一个整型数据占4个字节,则a+1的值为2000+4*4=2016(因为第0行有四个整型数据),a+1指向a[1],a+2指向a[2].a[0]+1代表a[0][1],a[0]+2代表a[0][2]
前面我们已经知道,a[0]和*(a+0)等价,a[1]和*(a+1)等价,所以a[0]+1和*(a+0)+1都是代表&a[0][1],a[0]+2和*(a+0)+2都是代表&a[0][2],既然a[0]+1和*(a+0)+1都是代表a[0][1]的地址那么*(a[0]+1)就是a[0][1]的值,同理,*(a[i]+j)和*(*(a+i)+j)都是a[i][j]的值。
有必要对a【i】的性质做进一步说明,a【i】如果是一维数组,那么就代表一维数组的第i个元素,如果是二维数组,那么a【i】就是一维数组名,它只是一个地址。
首先说明,a+1是二维数组a中序号为1的行的起始地址,而*(a+1)不是a+1单元的值,因为a+1并不是一个数组元素的地址,所以*(a+1)就是a【1】,也就是一维数组名,也就是地址,它指向a【1】【0】.
C语言的地址信息包括位置信息和数据类型信息,现在a【0】是一维数组名,它是一维数组中起始元素的地址,a是二维数组名,它是二维数组首行的起始地址,二者的纯地址是相同的,但他们的基类型不同,即他们指向的数据类型不同,前者是整型数据,后者是一维数组,如果用一个指针变量pt来指向此一维数组,应该这样定义:
int (*pt)[4];
表示pt指向由4个整型元素组成的一维数组,此指针变量的基类型是由4个整型元素组成的一维数组。
【例题】输出二维数组的有关数据(地址和元素的值)
#include<stdio.h>
int main()
{
int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
printf("%d ,%d\n",a,*a); //0行起始地址和0行0列元素地址
printf("%d ,%d\n",a[0],*(a+0)); //0行0列元素地址
printf("%d ,%d\n",&a[0],&a[0][0]); //0行起始地址和0行0列元素地址
printf("%d ,%d\n",a[1],a+1); //1行0列元素地址和1行起始地址
printf("%d ,%d\n",&a[1][0],*(a+1)+0); //1行0列元素地址
printf("%d ,%d\n",a[2],*(a+2)); //2行0列元素地址
printf("%d ,%d\n",&a[2],a+2); //2行起始地址
printf("%d ,%d\n",a[1][0],*(*(a+1)+0)); //1行0列元素的值
printf("%d ,%d\n",*a[2],*(*(a+2)+0)); //2行0列元素的值
return 0;
}
这里建议反复观看思考!!!!
运行结果:
(2)指向多维数组元素的指针变量
1.指向数组元素的指针变量
【例题】有一个3*4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值
#include<stdio.h>
int main()
{
int a[3][4]={1,3,5,7,9,11,13,15,17,1,21,23};
int *p;
for(p=a[0];p<a[0]+12;p++){ //使p依次指向下一个元素
if((p-a[0])%4==0){
printf("\n"); //p移动4次换行
}
printf("%4d",*p); //输出p指向的元素的值
}
return 0;
}
运行结果:
如果要输出某个指定的数值元素,则应该先计算该元素在数组中的相对位置,计算a【i】【j】在数组中的相对位置的计算公式为:i*m+j,其中m为二维数组的列数。
2.指向由m个元素组成的一维数组的指针变量
现在可以改用另一方法,使p不是指向整型变量,而是指向一个1包含m个元素的一维数组,这时,如果p先指向a【0】,则p+1不是指向a【0】【1】,而是指向a【1】.
【例题】输出二维数组任一行任一列元素的值
#include<stdio.h>
int main()
{
int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; //定义二维数组并初始化
int (*p)[4],i,j; //指针变量p指向包含4个整型元素的一维数组
p = a; //p指向二维数组的0行
scanf("%d %d",&i,&j); //输入行列号
printf("a[%d][%d]=%d\n",i,j,*(*(p+i)+j)); //输出
return 0;
}
运行结果:
注意:(*p)【4】不能写成*p【4】,*p【4】表示指针数组,由于方括号优先级比较高,所以p先与【4】结合为数组,然后再与*结合。
3.用指针数组做函数参数
一维数组名可以作为函数参数,多维数组名也可以,用指针变量作形参,以接受实参数组名传递来的地址,可以有俩种方法:1.用指向变量的指针变量 2.用指向一维数组的指针变量
【例题】有一个班,3个学生,各学四科,计算平均分数和第n个学生的成绩
#include<stdio.h>
void average(float *p,int n);
void search(float (*p)[4],int n);
int main()
{
float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}};
average(*score,12); //求平均分
search(score,2); //求序号为2的学生的成绩
return 0;
}
void average(float *p,int n)
{
float *p_end;
float sum=0,aver;
p_end=p+n-1;
for(;p<=p_end;p++){
sum=sum+(*p);
aver=sum/n;
}
printf("averag=%d\n",aver);
}
void search(float (*p)[4],int n)
{
int i;
printf("The score of No.%d are \n",n);
for(i=0;i<4;i++){
printf("%5.2f",*(*(p+n)+i));
}
}
调用search函数时实参是score(二维数组名,代表该数组中0行起始地址)传给p,使p也指向score[0]。p+n是score[n]的起始地址,*(p+n)+i是score[n][i]的地址。
【例题】在上一题的基础上,查找有一门以上不及格的学生,输出他们的全部课程和成绩
#include<stdio.h>
void search(float (*p)[4],int n); //函数声明
int main()
{
float score[3][4]={{65,57,70,60},{80,87,90,81},{90,99,100,98}};
search(score,3); //调用函数
return 0;
}
void search(float (*p)[4],int n)
{
int i,flag,j;
for(j=0;j<n;j++){
flag=0;
for(i=0;i<4;i++){
if(*(*(p+j)+i)<60){
flag=1;
}
}
if(flag==1){
printf("No.%d fails,his scores are:\n",j+1);
for(i=0;i<4;i++){
printf("%5.1f",*(*(p+j)+i));
}
printf("\n");
}
}
}
运行结果: