指针与函数
- 指针变量做函数参数
函数的参数不仅可以是整型、实型、字符型,还可以是指针型,它的作用是将一个变量的地址传送到另外一个函数中。
观察下面这段代码:
#include<stdio.h>
void swap(int *p1,int *p2){
int *p;
p=p1;
p1=p2;
p2=p;
}
int main(){
int a,b;
int *pa,*pb;
scanf("%d%d",&a,&b);
pa=&a;
pb=&b;
if(a<b)swap(pa,pb);
printf("%d,%d\n",*pa,*pb);
return 0;
}
我们来考虑通过改变指针形参的值能否改变指针实参的值?
答案是不能,即实参和形参间的数据是单项值传递的,指针做函数参数也遵循该原则。
再来观察下面的代码:
#include<stdio.h>
void swap(int *p1,int *p2){
int t;
t=*p1;
*p1=*p2;
*p2=t;
}
int main(){
int a,b;
int *pa,*pb;
scanf("%d%d",&a,&b);
pa=&a;
pb=&b;
if(a<b)swap(pa,pb);
printf("%d,%d\n",a,b);
return 0;
}
我们再来分析上述使用指针做形参和实参的问题。
可以看到我们把刚才swap函数里面交换p1和p2这两个指针变量的值改为交换p1、p2这两个指针变量所指的那两个单元的内容。即交换指针所指变量的值。如果此时输入两个数1、2那么输出的结果将是2,1。
结论:被调用函数不能改变实参指针变量的值,但可以改变实参指针变量所指向的变量的值。
- 数组名做函数参数
(我们知道形参与实参之间的数据传递是单向值传递方式,指针做函数参数也同样遵循这个原则,也就是说被调用函数不能改变实参指针变量的值,但是可以改变实参指针变量所指向变量的值,由于数组名就是数组的首地址)当用数组名作为参数时,如果形参数组中元素的值发生变化,实参数组元素的值也发生变化。即若有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有以下4种情况:
- 形参与实参都用数组名
- 实参用数组名,形参用指针变量
- 实参形参均用指针变量
- 实参为指针变量,形参为数组名
不管采用上述四种哪一种方式,他们都是地址的传递,只是形式不同而已。
注意:
指针变量在作实参时,必须有确定的值,即指向一个已定义的单元。如下面这种操作就是错误的:
实际上可以这么来做:
int main(){
int a[10],*p=a; //先定义一个一维数组,把数组的名字赋给指针变量
f(p,10);
......
}
下面举个栗子:用选择法对10个整数由小到大排序(四种方式实现)。
[方法1]:形参和实参都用数组名
#include<stdio.h>
void sort(int[],int);
int main(){
int i,a[10];
for(i=0;i<10;i++)
scanf("%d",&a[i]);
sort(a,10); //实参是数组名
for(i=0;i<10;i++)
printf("%3d",a[i]);
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[k];x[k]=x[i];x[i]=t;}
}
}
[方法2]:形参是数组名,实参是指针变量
#include<stdio.h>
void sort(int[],int);
int main(){
int i,a[10],*p;
p=a;
for(i=0;i<10;i++)
scanf("%d",p++);
p=a;
sort(p,10); //实参是指针
for(i=0;i<10;i++)
printf("%3d",*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[k];x[k]=x[i];x[i]=t;}
}
}
[方法3]:实参是数组名,形参是指针变量
#include<stdio.h>
void sort(int *,int);
int main(){
int i,a[10];
for(i=0;i<10;i++)
scanf("%d",&a[i]);
sort(a,10); //实参是数组名
for(i=0;i<10;i++)
printf("%3d",a[i]);
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+k);*(x+k)=*(x+i);*(x+i)=t;}
}
}
[方法4]:实参和形参都是指针变量
#include<stdio.h>
void sort(int *,int);
int main(){
int i,a[10],*p;
p=a;
for(i=0;i<10;i++)
scanf("%d",p++);
p=a;
sort(p,10); //实参为指针变量
for(i=0;i<10;i++)
printf("%3d",*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+k);*(x+k)=*(x+i);*(x+i)=t;}
}
}
下面这个栗子:编写函数,删除字符串中的给定字符。
#include<stdio.h>
void delete(char d[],char f);
int main(){
char str[]="How,are,you!",*p=str,c=',';
printf("original string is:%s\n",p);
delete(p,c);
printf("compressed string is:%s\n",str);
return 0;
}
void delete(char d[],char f){
int i=0,k=0;
while(d[i]!='\0'){
if(d[i]!=f)
d[k++]=d[i];
i++;
}
d[k]='\0';
}
在上述这个栗子中实参分别是字符型指针变量和字符型变量,对应的子函数的形参分别是字符型数组名和字符型变量。
- 返回指针的函数
返回指针的函数的定义形式:
类型说明符 *函数名([形式参数表]){
[说明部分]
语句
}
说明:表示函数的返回值是一个指针,其他和一般函数相同。如:
int *f(int x,int y){......}
看下面这个栗子:在给定的字符串s中,寻找一个特定的字符x,若找到x,则返回x在s中第一次出现的位置,并把s中该字符和该字符之前的字符按逆序输出。
#include<stdio.h>
char *str(char *s,char x);
int main(){
char s[40],*p,x;
gets(s);
x=getchar();
p=str(s,x);
if(*p){
printf("%c",*p);
while(p-s){
p--;
printf("%c",*p);
}
}
else
printf("char %c not found",x);
return 0;
}
char *str(char *s,char x){
int c=0;
while(x!=s[c]&&s[c]!='\0')
c++;
return (&s[c]);
}
运行结果如下:
指针数组
指针数组:数组中的元素均为指针类型。适合用来指向字符串
定义形式:数据类型 *数组名[常量表达式]
如int *pa[6];
注意:下标运算符[]比*优先级高,因此pa先与[6]结合,形成pa[6],具有6个元素。然后再与*
结合,表示此数组中每一个元素都是指针型的。功能即定义一个由6个指针变量构成的指针数组,数组中的每一个元素都是一个指向一个整数的指针变量。
指针数组的初始化:必须用地址值为指针数组初始化。如:
int a[3][3]={1,2,3,4,5,6,7,8,9},*pa[3];
pa[0]=a[0];
pa[1]=a[1];
pa[2]=a[2];
指针数组pa[3]相当于有三个指针,*pa[0],*pa[1],*pa[2]
,上面初始化的结果:
这就使得这三个指针分别指向了这个二维数组的每一行,这三个指针的初值就是这个二维数组的每一行的首地址。
注意: int *p[5];
与int (*p)[5];
不同。前者是指针数组而后者改变了运算顺序,先运算括号内的说明p是一个指针,之后再结合下标运算符,说明这个指针变量指向尺寸是5的数组。
指针数组的使用:用指针数组和用数组地址引用数组元素是等价的。
上面这些用法都是等价的,区别在于pa[i]的值可变,而a[i]的值不可变。
下面这个栗子实现了求N阶方阵副对角线上的元素之和。
#include<stdio.h>
#define N 4
int main(){
int a[N][N],i,j,sum=0,*p[N];
for(i=0;i<N;i++)
p[i]=a[i];
for(i=0;i<N;i++)
for(j=0;j<N;j++)
scanf("%d",p[i]+j);
for(i=0;i<N;i++)
for(j=0;j<N;j++)
if(i+j==N-1)
//副对角元素上的元素行号与列号相加恰好
//就是二维数组的最大的元素下标,也就是说i+j=n-1。
sum+=p[i][j];
printf("sum=%d\n",sum);
return 0;
}
- 指针与字符串数组
字符串数组:数组中的每个元素都是存放字符的数组,也就是说字符串数组的每一行可存放一个字符串。
可以用赋初值的方式给字符串数组赋值。有两种方法:
- 直接给字符串数组赋初值
- 用指针数组处理多个字符串
直接给字符串数组赋初值:
char b[4][8]={"VC","FORTRAN","BASIC","Foxpro"};
存储形式如下:
由于字符串长短不一,定义时应考虑最长的串和结束标志的位置。防止造成内存单元浪费。
用指针数组处理多个字符串
若有定义:
char *f[4]={"VC","FORTRAN","BASIC","Foxpro"};
此定义还可以写成:
char *f[]={"VC","FORTRAN","BASIC","Foxpro"};
则数组f中的每个元素都存放着对应的一个字符串的首地址,各字符串依次存入各相应的首地址开始的连续存储单元中。
与第一种方法相比它不需要分配32个存储空间,大大节省了存储空间!
下面这个栗子实现了用指针数组输出多个字符串。
#include<stdio.h>
int main(){
char *s[4]={"cat","pig","dog","all animals"};
int i;
for(i=0;i<4;i++)
printf("%s\n",s[i]);
return 0;
}
下面这个栗子实现了将4个字符串从小到大排序后输出。
#include<stdio.h>
#include<string.h>
void fsort(char *color[],int n);
int main(){
int i;
char *p[]={"red","blue","yellow","green"};
fsort(p,4);
for(i=0;i<4;i++)
printf("%s\n",p[i]);
return 0;
}
//冒泡排序
void fsort(char *color[],int n){
int k,j;
char *temp;
for(k=1;k<n;k++)
for(j=0;j<n-k;j++)
if(strcmp(color[j],color[j+1])>0){
temp=color[j];
color[j]=color[j+1];
color[j+1]=temp;
}
}
初始时:
排序后:
指针小结
int *p; //p为指向整型数据的指针变量
int *p[N]; //定义指针数组p,它由N个指向整数的指针元素组成
int (*p)[N]; //定义指向N个元素的一维数组的指针变量p
int *p(); //p为带回一个指针的函数,该指针指向整型数据
int (*p)(); //p为指向函数的指针,该函数返回一个整型值
指针在使用前一定要赋值,指针可以指向任何数据类型,指向谁,就存谁的地址。
必须用地址值为指针变量初始化(&变量名,或数组名),不允许用整数。
相同类型的指针可以相互赋值。
优点:快速灵活、可实现动态分配;缺点:易出错。