C语言指针学习(2)

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");
		}
	}
}

运行结果:

 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值