1、数组元素的指针
一个变量有地址,一个数组包含若干元素,每个数组元紫都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某元素的地址放到一个指针变量中)。所谓数组元索的指针就是数组元素的地址。引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。
/*定义一个指向数组元素的指针变量的方法*/ int a[10]; //定义a为包含10个整型数据的数组 int *p; //定义p为指向整型变量的指针变量 /*应当注意:如果数组为int型,则指针变量的基类型也为int型,下面对该指针变量赋值*/ p=&a[0]; //把a[0]元素的地址赋给指针变量p /*C语言规定,数组名代表数组中首元素的地址*/ p=&a[0] <=> p=a //等价关系 /*注意数组名a不代表整个数组,上述p=a的作用是把a数组元素的首地址赋给指针变量p,而不是将数组a各元素的值赋给p*/
2、通过指针引用数组元素
假设p已定义为一个指向整型数据的指针变量,并已给它赋了一个整型数组元素的地址,使它指向某一个数组元素。如果有赋值语句:*p=1; 表示将1赋给ρ当前所指向的数组元素。按C语言的规定:如果指针变量p已指向数组中的一个元素,则p+1指向同数组中的下一个元素而不是将P的值(地址)简单地加1。例如,数组元素是float型,每个元素占4个字节,则p+1意味着使p的值(是地址)加4个字节,以使它指向下一元素。p+1所代表的地址实际上是p+1*d,d是一个数组元素所占的字节数(在TurboC++中,对int型,d=2;对float和long型,d=4;对char型,d=1。在VisualC++ 6. 0中,对int.long和float型,d=4;对char型,d=1)。
如果p的初值为&.a[0],则:
(1) p+i和a+i就是a[i]的地址或者说,它们指向a数组的第i个元素,这里需要特别注意的是a代表数组首元素的地址,a+i也是地址,它的计算方法同p+i,即它的实际地址为a+iXd。例如,p+9 和a+9的值是&a[9],它指向a[9]。
(2) *(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。例如*(p+5)或*(a+5)就是a[5]。即*(p+5)、*(a+5)、a[5]三者等价。实际上,在编译时,对数组元素a[i]就是按*(a+i)处理的,即按数组首元素的地址加上相对位移量得到要找的元素的地址,然后找出该单元中的内容。若数组a的首元素的地址为1000,设数组为float 型,则a[3]的地址是这样计算的:1000+3X4= 1012,然后从1012地址所指向的float型单元取出元素的值,即a[3]的值。可以看出,[ ]实际上是变址运算符,即将a[i]按a+i计算地址,然后找出此地址单元中的值。
(3) 指向数组的指针变量也可以带下标,如:p[i]与*(p+i)等价。引用一个数组元素有两种方法, ①. 下标法,如a[i]形式。②. 指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值:p=a。
//输出数组中的全部元素 /*假设有一个a数组,整型,有10个元素。要输出各元素的值有以下三种方法:*/ //1、下标法 #include<stdio.h> int main(){ int a[10]; for(int i=0;i<10;i++) scanf("%d",&a[i]); printf("\n下标法输出:\n"); for(int i=0;i<10;i++) printf("%d ",a[i]); printf("\n"); return 0; } //通过数组名计算数组元素地址,找出元素的值 #include<stdio.h> int main(){ int a[10]; for(int i=0;i<10;i++) scanf("%d",&a[i]); printf("\n"); for(int i=0;i<10;i++) printf("%d ",*(a+i)); return 0; } //用指针变量指向数组元素 #include<stdio.h> int main(){ int a[10]; int *p; for(int i=0;i<10;i++) scanf("%d",&a[i]); for(p=a;p<a+10;p++) printf("%d ",*p); return 0; }
(1) 对上述的第①和第②种方法执行效率是相同的。C编译系统是将a[i]转换为*(a+i)处理的,即先计算元素地址。因此用第①和第②种方法找数组无素费时较多。
(3) 用下标法比较直观,能直接知道是第几个元素。例如,[5]是数组中序号为5的元素(注意序号从0算起)。用地址法或指针变量的方法不直观,难以很快地判断出当前处理的是哪一个元素。例如,上述第③种方法所用的程序,要仔细分析指针变量p的当前指向,才能判断当前输出的是第几个元素。
① 可以通过改变指针变量的值指向不同的元素。例如,上述第③种方法是用指针变量p来指向元素,用p+ +使p的值不断改变从而指向不同的元素,这是合法的。如果不用p而使数组名a变化(例如,用a++)行不行呢?假如将上述第③种方法中的程序的最后两行改为:
for(p=a;a<(p+ 10);a+ +) printf("%d", *a);是不行的。因为数组名a代表数组首元素的地址,它是一个指针常量,它的值在程序运行期间是固定不变的。既然a是常量,所以a+ +是无法实现的。
//通过指针变量输出a数组的10个元素 #include<stdio.h> int main(){ int *p,a[10]; p=a; for(int i=0;i<10;i++) scanf("%d",p++); printf("\n"); for(int i=0;i<10;i++,p++) printf("%d ",*p); printf("\n"); return 0; }
运行结果如下:
显然输出的数值并不是a数组中各元素的值。原因是指针变量的初始值为a数组首元素(即a[0])的地址,但经过第一个for循环读人数据后,p已指向a数组的末尾,因此,在执行第二个for循环时,p的起始值不是&a[0]了,而是a+10。由于执行第二个for 循环时,每次要执行p++,因此p指向的是a数组下面的10个元素,而这些存储单元中的值是不可预料的。解决这个问题的办法,只要在第二个for循环前加上一个赋值语句:p=a; 使p的初始值回到&a[0],这样结果就对了,程序如下:
#include<stdio.h> int main(){ int *p,a[10]; p=a; for(int i=0;i<10;i++) scanf("%d",p++); printf("\n"); p=a; //使p回到初始值 for(int i=0;i<10;i++,p++) printf("%d ",*p); printf("\n"); return 0; }
运行结果如下:
③ 从上例可以看到,虽然定义数组时指定它包含10个元素,并用p指向某一数组元素,但是实际上指针变量p可以指向数组以后的内存单元。如果引用数组元素a[10],C编译程序并不认为非法,系统把它按*(a+10)处理,即先找出(a+10)的值(是一个地址),然后找出它指向的单元的内容。这样做虽然是合法的(在编泽时不出错),但应避免出现这样的情况,这会使程序得不到预期的结果。这种错误比较隐蔽,初学者往往难以发现。在使用指针变量指向数组元索时,应切实保证指向数组中有效的元素。注意:*(p++)与*(+ +p)作用不同。前者是先取*p值,然后使p加1。后者是先使p加1,再取*p。若p初值为a(即&a[0]),则*(p++)为a[0],而*(++p)为a[1]。++(* p)表示P所指向的元素值加1,如果p=a,则++(*p)相当于++a[0]。若a[0]=3,则在执行++(*p)(即++a[0])后,a[0]的值为4。注意:是元素值a[0]加1.而不是指针p的值加1。
3、用数组名作函数参数
void main(){ void f(int arr[],int n); int array[10]; .... f(array,10); .... } void f(int arr[],int n){ .... }
array为实参数组名,arr为形参数组名。当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组中的值随之变化。先看数组元素作实参时的情况,如果已定义个函数,其原型为:
void swap(int x,int y);
假设函数的作用是 将两个形参(x.y)的值交换,今有以下的函数调用:
swap(a[1].,a[2]);
用数组元素a[1]、a[2]作实参的情况与用变最作实参时一样,是“值传递”方式,将a[1]和a[2]的值单向传递给x和y。当x和y的值改变时a[1]和a[2]的值并不改变。再看用数组名作函数参数的情况。我们知道,实参数组名代表该数组首元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的。因此,形参应该是一个指针变量(只有指针变量才能存放地址)。实际上,C编译都是将形参数组名作为指针变量来处理的。例如,上面给出的函数f的形参是写成数组形式的:
f(int arr[ ],int n);
但在编译时是将arr按指针变量处理的,相当于将函数f的首部写成:
f(int *arr, int n)
以上两种写法是等价的。在该函数被调用时,系统会建立一个指针变量arr,用来存放从主调函数传递过来的实参数组首元素的地址。当arr接收了实参数组的首元素地址后,arr就指向实参数组首元素,也就是指向array[0]。因此,* arr就是array[0]。arr+1指向array[1],arr+2指向array[2],arr+3指向array[3]。也就是说,* (arr+1)、* (arr+2)、* (arr+3)分别是array[1]、array[2]、array[3]。我们知道,* (arr+i)和arr[i]是无条件等价的。因此,在调用两数期间,arr[0]和* arr以及array[0]都代表数组array序号为0的元素,依此类推,arr[3]、* (arr+ 3)、array[3]都代表array数组序号为3的元素。
常用这种方法通过调用一个函数来改变实参数组的值。
需要说明的是C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为的数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢?这是因为在C语言中用下标法和指针法都可以访问一个数组(如果有一个数组a,则a[i]和* (a+i)无条件等价),用下标法表示比较直观,便于理解。因此许多人愿意用数组名作形參,以便与实参数组对应。从应用的角度看,用户可以认为有一个形参数组它从实参数组那里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就是改变了实参数组的值。当然在主调函数中可以利用这些已改变的值。应该注意:实参数组名代表一个固定的地址或者说是指针常量,但形参数组并不是一个固定的地址值,而是作为指针变量,在函数调用开始时,它的值等于实参数组首元素的地址,在函数执行期间,它可以再被赋值。例如:
void f(int arr[],int n){ printf("%d\n",*arr);//输出array[0]的值 arr=arr+3; printf("%d\n",*arr);//输出array[3]的值 }
//实现将数组a中n个整数按相反的顺序存放 #include<stdio.h> void stran(int x[],int n){ int temp,i,j; int m=(n-1)/2; for(i=0;i<=m;i++){ j=n-1-i; temp=x[i]; x[i]=x[j]; x[j]=temp; } } int main(){ int a[9]={1,2,3,4,5,6,7,8,9}; printf("初始数组: "); for(int i=0;i<9;i++) printf("%d ",a[i]); stran(a,9); printf("\n对换后的数组: "); for(int i=0;i<9;i++) printf("%d ",a[i]); return 0; }
运行结果:
对这个程序可以作一些改动。将函数stran中的形参x改成指针变量。实参为数组名a,即数组a首元素的地址,将它传给形参指针变量x,这时x就指向a[0]。x+m是a[m]元素的地址。设i和j以及p都是指针变量,用它们指向有关元素。i的初值为x,j的初值为x+n-1,使*i与*j交换就是使a[i]与a[j]交换。
#include<stdio.h> void stran(int *x,int n){ int m,*i,*j,temp; m=(n-1)/2; i=x; j=x+n-1; for(;i<=x+m;i++,j--){ temp=*i; *i=*j; *j=temp; } } int main(){ int a[9]={1,2,3,4,5,6,7,8,9}; printf("初始数组: "); for(int i=0;i<9;i++) printf("%d ",a[i]); stran(a,9); printf("\n对换后的数组: "); for(int i=0;i<9;i++) printf("%d ",a[i]); return 0; }
运行结果和上一程序一样。
用实参指针变量改写上述程序,代码如下:
#include<stdio.h> void inv(int *x,int n){ int temp,*i,*j; int m=(n-1)/2; i=x; j=x+n-1; for(;i<=x+m;i++,j--){ temp=*i; *i=*j; *j=temp; } return ; } int main(){ int i,arr[10],*p=arr; printf("初始数组:\n"); for(i=0;i<10;i++,p++) scanf("%d",p); printf("\n"); p=arr; inv(p,10); printf("交换后数组:\n"); for(p=arr;p<arr+10;p++){ printf("%d ",*p); } printf("\n"); return 0; }
运行结果:
注意:上面的main函数中的指针变量p是有确定值的。如果在main函数中不设数组,只有指针变量,就会出错,假如主函数做如下修改,编译时会出错,原因是指针变量arr没有确定值,谈不上指向哪个变量。
int main(){ void f(int *x,int n); int *arr; for(int i=0;i<10;i++) scanf("%d",arr+i); f(arr,10); for(i=0;i<10;i++) printf("%d ",*(arr+i); return 0; }
//用选择法对10个整数按由大到小的顺序排序 #include<stdio.h> 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; } } } int main(){ int *p,i,a[10]; p=a; for(i=0;i<10;i++,p++) scanf("%d",p); printf("\n"); p=a; sort(p,10); for(p=a;p<a+10;p++) printf("%d ",*p); return 0; }
为了便于理解,函数sort中用数组名作为形参,用下标法引用形参数组元素。这样的程序很容易看懂。当然也可以改用指针交量这时sort 函数的首部可以改为:
sort(int *x,int n)
其他不改,程序运行结果不变。可以看到,即使在函数sort中将x定义为指针变量,在函数中仍可用x[i]、x[k]这样的形式表示数组元素,它就是x+i和x+k所指的数组元素。上面的sort函数等价于:
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+i)>*(x+j)) k=j; } if(k!=j){ t=*(x+i); *(x+i)=*(x+j); *(x+j)=t; } } }
来源:https://www.cnblogs.com/geziyu/p/9670237.html