第8章 善于利用指针
指针是什么
如果在程序中定义了一个变量,在对程序进行编译时,系统就会给该变量分配内存单元
编译系统根据程序中定义的变量类型,分配一定长度的空间
例如,VC++为整型变量分配4个字节,对单精度浮点型变量分配4个字节,对字符型变量分配1个字节
地址指向该变量单元
将地址形象化地称为“指针”
直接存取
int i=3,j=6,k;
k=i+j;
间接存取
int i=3,j=6,k;
[ 定义特殊变量 i_pointer ]
i_pointer=&i;
*i_pointer=50;
指向就是通过地址来体现的
假设i_pointer中的值是变量i的地址(2000),这样就在i_pointer和变量i之间建立起一种联系,即通过i_pointer能知道i的地址,从而找到变量i的内存单元
例如,地址2000是变量i的指针
如果有一个变量专门用来存放另一变量的地址(即指针),则它称为“指针变量”
i_pointer就是一个指针变量。指针变量就是地址变量,用来存放地址的变量,指针变量的值是地址(即指针)
“指针”和“指针变量”是不同的概念
可以说变量i的指针是2000,而不能说i的指针变量是2000
指针是一个地址,而指针变量是存放地址的变量
通过指针变量访问整型变量。
思想:
先定义2个整型变量,再定义2个指针变量,分别指向这两个整型变量,通过访问指针变量,可以找到它们所指向的变量,从而得到这些变量的值。
#include <stdio.h>
int main()
{ int a=100,b=10;
int *pointer_1, *pointer_2;
pointer_1=&a;
pointer_2=&b;
printf(“a=%d,b=%d\n”,a,b);
printf(“*pointer_1=%d,*pointer_2=
%d\n”,*pointer_1,*pointer_2); //此处*与指针变量一起使用。此时代表指针变量所指向的变量
return 0;
}
定义指针变量的一般形式为:
类型 * 指针变量名;
如:int *pointer_1, *pointer_2;
int是为指针变量指定的“基类型”
基类型指定指针变量可指向的变量类型
如pointer_1可以指向整型变量,但不能指向浮点型变量
在引用指针变量时,可能有三种情况:
给指针变量赋值。如:p=&a;
引用指针变量指向的变量。如有
p=&a; *p=1;
则执行printf(“%d”,*p); 将输出1
引用指针变量的值。如:printf(“%o”,p);
运算符
(1) & 取地址运算符。
&a是变量a的地址
(2) * 指针运算符(“间接访问”运算符)
如果: p指向变量a,则*p就代表a。
k=*p; (把a的值赋给k)
*p=1; (把1赋给a)
输入a和b两个整数,按先大后小的顺序输出a和b。
思路:
用指针方法来处理这个问题。不交换整型变量的值,而是交换两个指针变量的值。
#include <stdio.h>
int main()
{ int *p1,*p2,*p,a,b;
printf(“integer numbers:");
scanf(“%d,%d”,&a,&b);
p1=&a; p2=&b;
if(a<b)
{ p=p1; p1=p2; p2=p; }
printf(“a=%d,b=%d\n”,a,b);
printf(“%d,%d\n”,*p1,*p2);
return 0;
}
题目要求同上例,即对输入的两个整数按大小顺序输出。现用函数处理,而且用指针类型的数据作函数参数。
思路
定义一个函数swap,将指向两个整型变量的指针变量作为实参传递给swap函数的形参指针变量,在函数中通过指针实现交换两个变量的值。
#include <stdio.h>
int main()
{ void swap(int *p1,int *p2);
int a,b; int *pointer_1,*pointer_2;
printf("please enter a and b:");
scanf(“%d,%d”,&a,&b);
pointer_1=&a;
pointer_2=&b;
if (a<b) swap(pointer_1,pointer_2);
printf(“max=%d,min=%d\n”,a,b);
return 0;
}
void swap(int *p1,int *p2)
{ int temp;
temp=*p1;
*p1=*p2;
*p2=temp;
}
输入3个整数a,b,c,要求按由大到小的顺序将它们输出。用函数实现。#include <stdio.h>
int main()
{ void exchange(int *q1, int *q2, int *q3);
int a,b,c,*p1,*p2,*p3;
scanf("%d,%d,%d",&a,&b,&c);
p1=&a; p2=&b; p3=&c;
exchange(p1,p2,p3);
printf(“%d,%d,%d\n",a,b,c);
return 0;
}
void exchange(int *q1, int *q2, int *q3)
{ void swap(int *pt1, int *pt2);
if(*q1<*q2) swap(q1,q2);
if(*q1<*q3) swap(q1,q3);
if(*q2<*q3) swap(q2,q3);
}
void swap(int *pt1, int *pt2)
{ int temp;
temp=*pt1; *pt1=*pt2; *pt2=temp;
}
通过指针引用数组
一个变量有地址,一个数组包含若干元素,每个数组元素都有相应的地址
指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中)
所谓数组元素的指针就是数组元素的地址
可以用一个指针变量指向一个数组元素
int a[10]={1,3,5,7,9,11,13,15,17,19};
int *p;
p=&a[0];
等价于p=a;
等价于int *p=a;
或int *p=&a[0];
运算
在指针指向数组元素时,允许以下运算:
加一个整数(用+或+=),如p+1,同一数组中的下一个元素
减一个整数(用-或-=),如p-1,同一数组中的上一个元素
自加运算,如p++,++p
自减运算,如p--,--p
两个指针相减,如p1-p2 (只有p1和p2都指向同一数组中的元素时才有意义)
有一个整型数组a,有10个元素,要求输出数组中的全部元素。
思路:
引用数组中各元素的值有3种方法:
(1) 下标法;
(2) 通过数组名计算数组元素地址,找出元素的值;
(3) 用指针变量指向数组元素
分别写出程序,以资比较分析。
(1) 下标法
#include <stdio.h>
int main()
{ int a[10]; int i;
printf(“enter 10 integer numbers:\n");
for(i=0;i<10;i++) scanf("%d",&a[i]);
for(i=0;i<10;i++) printf(“%d ”,a[i]);
printf("%\n");
return 0;
}
(2) 通过数组名计算数组元素地址,找出元素的值
#include <stdio.h>
int main()
{ int a[10]; int i;
printf(“enter 10 integer numbers:\n");
for(i=0;i<10;i++) scanf("%d",&a[i]);
for(i=0;i<10;i++)
printf(“%d ”,*(a+i));
printf("\n");
return 0;
}
(3) 用指针变量指向数组元素
#include <stdio.h>
int main()
{ int a[10]; int *p,i;
printf(“enter 10 integer numbers:\n");
for(i=0;i<10;i++) scanf("%d",&a[i]);
for(p=a; p<(a+10); p++)
printf(“%d ”,*p);
printf("\n");
return 0;
}
通过指针变量输出整型数组a的10个元素。
#include <stdio.h>
int main()
{ int *p,i,a[10];
p=a;
printf(“enter 10 integer numbers:\n");
for(i=0;i<10;i++) scanf(“%d”,p++);
for(i=0;i<10;i++,p++)
printf(“%d ”,*p);
printf("\n");
return 0;
}
将数组a中n个整数按相反顺序存放
#include <stdio.h>
int main()
{ void inv(int x[ ],int n);
int i, a[10]={3,7,9,11,0,6,7,5,4,2};
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]);
printf("\n");
return 0;
}
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;
}
}
void inv(int x[ ],int n)
{ int temp,*i,*j;
i=x; j=x+n-1;
for( ; i<j; i++,j--)
{ temp=*i; *i=*j; *j=temp; }
}
改写
用指针变量作实参。
#include <stdio.h>
int main()
{ void inv(int *x,int n);
int i, arr[10],*p=arr;
for(i=0;i<10;i++,p++)
scanf(“%d”,p);
inv(p,10);
for(p=arr;p<arr+10;p++)
printf(“%d ”,*p);
printf("\n");
return 0;
}
用指针方法对10个整数按由大到小顺序排序。
思路:
在主函数中定义数组a存放10个整数,定义int *型指针变量p指向a[0]
定义函数sort使数组a中的元素按由大到小的顺序排列
在主函数中调用sort函数,用指针p作实参
用选择法进行排序
#include <stdio.h>
int main()
{ void sort(int x[ ],int n);
int i,*p,a[10];
p=a;
for(i=0;i<10;i++) scanf(“%d”,p++);
p=a;
sort(p,10);
for(p=a,i=0;i<10;i++)
{ printf(“%d ”,*p); p++; }
printf("\n");
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; }
}
}
通过指针引用多维数组
- 多维数组元素的地址
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
a代表第0行首地址
a+1代表第1行首地址
a+2代表第2行首地址
a+i代表行号为i的行首地址(按行变化)
a[0]代表a[0][0]的地址
a[0]+1代表a[0][1]的地址
a[0]+2代表a[0][2]的地址
a[0]+3代表a[0][3]的地址
例8.11 二维数组的有关数据(地址和值)
#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);
printf(“%d,%d\n”,a[0],*(a+0));
printf(“%d,%d\n”,&a[0],&a[0][0]);
printf(“%d,%d\n”,a[1],a+1);
printf(“%d,%d\n”,&a[1][0],*(a+1)+0);
printf(“%d,%d\n”,a[2],*(a+2));
printf(“%d,%d\n”,&a[2],a+2);
printf(“%d,%d\n”,a[1][0],*(*(a+1)+0));
printf(“%d,%d\n”,*a[2],*(*(a+2)+0));
return 0;
}
有一个3×4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值。
#include <stdio.h>
int main()
{ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
int *p;
for(p=a[0];p<a[0]+12;p++)
{ if((p-a[0])%4==0) printf(“\n”);
printf(“%4d”,*p);
}
printf("\n");
return 0;
}
输出二维数组任一行任一列元素的值。
#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=a;
printf(“enter row and colum:");
scanf(“%d,%d”,&i,&j);
printf(“a[%d,%d]=%d\n”, i,j,*(*(p+i)+j));
return 0;
}
有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。
#include <stdio.h>
int main()
{ void average(float *p,int n);
void search(float (*p)[4],int n);
float score[3][4]={{65,67,70,60},
{80,87,90,81},{90,99,100,98}};
average(*score,12);
search(score,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("average=%5.2f\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));
printf("\n");
}
在上题基础上,查找有一门以上课程不及格的学生,输出他们的全部课程的成绩。
#include <stdio.h>
int main()
{ void search(float (*p)[4],int n);
float score[3][4]={{65,57,70,60}, {58,87,90,81},{90,99,100,98}};
search(score,3);
return 0;
}
void search(float (*p)[4],int n)
{ int i,j,flag;
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\n",j+1);
for(i=0;i<4;i++)
printf(“%5.1f ”,*(*(p+j)+i));
printf("\n");
}
}
}
通过指针引用字符串
定义一个字符数组,在其中存放字符串“I love China!”,输出该字符串和第8个字符。
#include <stdio.h>
int main()
{
char string[]=“I love China!”;
printf(“%s\n”,string);
printf(“%c\n”,string[7]);
return 0;
}
通过字符指针变量输出一个字符串。
#include <stdio.h>
int main()
{
char *string=“I love China!”;
printf(“%s\n”,string);
return 0;
}
将字符串a复制为字符串b,然后输出字符串b。
#include <stdio.h>
int main()
{ char a[ ]=“I am a student.”, b[20];
int i;
for(i=0;*(a+i)!=‘\0‘;i++)
*(b+i)=*(a+i);
*(b+i)=‘\0’;
printf(“string a is:%s\n”,a);
printf("string b is:");
for(i=0;b[i]!=‘\0‘;i++)
printf(“%c”,b[i]);
printf("\n");
return 0;
}
char *format;
format=”a=%d,b=%f\n”;
printf(format,a,b);
相当于
printf(“a=%d,b=%f\n”,a,b);
指向函数的指针
用函数求整数a和b中的大者。
思路:
定义一个函数max,实现求两个整数中的大者。在主函数调用max函数,除了可以通过函数名调用外,还可以通过指向函数的指针变量来实现。分别编程并作比较。
(1) 通过函数名调用函数
#include <stdio.h>
int main()
{ int max(int,int);
int a,b,c;
printf("please enter a and b:");
scanf("%d,%d",&a,&b);
c=max(a,b);
printf(“%d,%d,max=%d\n",a,b,c);
return 0;
}
int max(int x,int y)
{ int z;
if(x>y) z=x;
else z=y;
return(z);
}
(2) 通过指针变量访问它所指向的函数
#include <stdio.h>
int main()
{ int max(int,int);
int (*p)(int,int); int a,b,c;
p=max;
printf("please enter a and b:");
scanf("%d,%d",&a,&b);
c=(*p)(a,b);
printf(“%d,%d,max=%d\n",a,b,c);
return 0;
}
定义指向函数的指针变量的一般形式为
数据类型 (*指针变量名)(函数参数表列);
例如:
int (*p)(int,int);
p=max; 对
p=max(a,b); 错
p+n,p++,p-- 等运算无意义
输入两个整数,然后让用户选择1或2,选1时调用max函数,输出二者中的大数,选2时调用min函数,输出二者中的小数。
#include <stdio.h>
int main()
{ int max(int,int); int min(int x,int y);
int (*p)(int,int); int a,b,c,n;
scanf("%d,%d",&a,&b);
scanf("%d",&n);
if (n==1) p=max;
else if (n==2) p=min;
c=(*p)(a,b);
printf("a=%d,b=%d\n",a,b);
if (n==1) printf("max=%d\n",c);
else printf("min=%d\n",c);
return 0;
}
int max(int x,int y)
{ int z;
if(x>y) z=x;
else z=y;
return(z);
}
int min(int x,int y)
{ int z;
if(x<y) z=x;
else z=y;
return(z);
}
指向函数的指针变量的一个重要用途是把函数的地址作为参数传递到其他函数
指向函数的指针可以作为函数参数,把函数的入口地址传递给形参,这样就能够在被调用的函数中使用实参函数
有两个整数a和b,由用户输入1,2或3。如输入1,程序就给出a和b中大者,输入2,就给出a和b中小者,输入3,则求a与b之和。
#include <stdio.h>
int main()
{ void fun(int x,int y, int (*p)(int,int));
int max(int,int); int min(int,int);
int add(int,int); int a=34,b=-21,n;
printf("please choose 1,2 or 3:");
scanf(“%d”,&n);
if (n==1) fun(a,b,max);
else if (n==2) fun(a,b,min);
else if (n==3) fun(a,b,add);
return 0;
}
int fun(int x,int y,int (*p)(int,int))
{ int resout;
resout=(*p)(x,y);
printf(“%d\n”,resout);
}
int max(int x,int y)
{ int z;
if(x>y) z=x;
else z=y;
printf("max=" );
return(z);
}
int min(int x,int y)
{ int z;
if(x<y) z=x;
else z=y;
printf("min=");
return(z);
}
int add(int x,int y)
{ int z;
z=x+y;
printf("sum=");
return(z);
}
定义返回指针值的函数的一般形式为
类型名 *函数名(参数表列);
例:int *a(int x, int y);
有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数实现。
#include <stdio.h>
int main()
{ float score[ ][4]={{60,70,80,90},
{56,89,67,88},{34,78,90,66}};
float *search(float (*pointer)[4], int n);
float *p; int i,k;
scanf("%d",&k);
printf("The scores of No.%d are:\n",k);
p=search(score,k);
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i));
printf("\n");
return 0;
}
float *search(float (*pointer)[4],int n)
{ float *pt;
pt=*(pointer+n);
return(pt);
}
找出其中有不及格的课程的学生及其学生号。
……
float *search(float (*pointer)[4]);
float *p; int i,j;
for(i=0;i<3;i++)
{ p=search(score+i);
if(p==*(score+i))
{ printf("No.%d score:",i);
for(j=0;j<4;j++)
printf(“%5.2f ”,*(p+j));
printf("\n");
}
}
……
float *search(float (*pointer)[4])
{ int i=0;
float *pt;
pt=NULL;
for( ;i<4;i++)
if(*(*pointer+i)<60)
pt=*pointer;
return(pt);
}
指针数组
一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
定义一维指针数组的一般形式为
类型名*数组名[数组长度];
int *p[4];
将若干字符串按字母顺序(由小到大)输出。
#include <stdio.h>
#include <string.h>
int main()
{ void sort(char *name[ ],int n);
void print(char *name[ ],int n);
char *name[ ]={“Follow”,“Great”, “FORTRAN”,“Computer”};
int n=4;
sort(name,n);
print(name,n);
return 0;
}
void sort(char *name[ ],int n)
{ char *temp; int i,j,k;
for (i=0;i<n-1;i++)
{ k=i;
for (j=i+1;j<n;j++)
if(strcmp(name[k],name[j])>0) k=j;
if (k!=i)
{ temp=name[i]; name[i]=name[k];
name[k]=temp;
}
}
}
void print(char *name[ ],int n)
{ int i;
for(i=0;i<n;i++)
printf(“%s\n”,name[i]);
}
使用指向指针数据的指针变量。
char *name[]={“Follow”,,“Great”, “FORTRAN”,“Computer”};
char **p; int i;
for(i=0;i<5;i++)
{ p=name+i; printf("%s\n",*p); }
有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组各元素的值。
#include <stdio.h>
int main()
{ int a[5]={1,3,5,7,9};
int *num[5]={&a[0],&a[1],&a[2], &a[3],&a[4]};
int **p,i;
p=num;
for (i=0;i<5;i++)
{ printf("%d ",**p);
p++;
}
printf("\n"); return 0;
}
动态内存分配与指向它的指针变量
内存的动态分配
非静态的局部变量是分配在内存中的动态存储区的,这个存储区是一个称为栈的区域
C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据需要时随时开辟,不需要时随时释放。这些数据是临时存放在一个特别的*存储区,称为堆区
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这4个函数。
malloc函数
其函数原型为:void *malloc(unsigned int size);
其作用是在内存的动态存储区中分配一个长度为size的连续空间
函数的值是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置
malloc(100);
开辟100字节的临时分配域,函数值为其第1个字节的地址
注意指针的基类型为void,即不指向任何类型的数据,只提供一个地址
如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)
calloc函数
其函数原型为:void *calloc(unsigned n,unsigned size);
其作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的起始位置的指针;如果分配不成功,返回NULL。如:
如: p=calloc(50,4); 开辟50×4个字节的临时分配域,把起始地址赋给指针变量p
free函数
其函数原型为:void free(void *p);
其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。
例如:free(p);
释放指针变量p所指向的已分配的动态空间
free函数无返回值
realloc函数
其函数原型为:void *realloc(void *p,unsigned int size);
如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用recalloc函数重新分配。
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。
例如:realloc(p,50);
将p所指向的已分配的动态空间改为50字节
以上4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用“#include <stdlib.h>”指令把stdlib.h头文件包含到程序文件中。
例8.30 建立动态数组,输入5个学生的成绩,另外用一个函放数检查其中有无低于60分的,输出不合格的成绩。
思路:用malloc函数开辟一个动态*区域,用来存5个学生的成绩,会得到这个动态域第一个字节的地址,它的基类型是void型。用一个基类型为int的指针变量p来指向动态数组的各元素,并输出它们的值。但必须先把malloc函数返回的void指针转换为整型指针,然后赋给p1
#include <stdio.h>
#include <stdlib.h>
int main()
{ void check(int *);
int *p1,i;
p1=(int *)malloc(5*sizeof(int));
for(i=0;i<5;i++)
scanf("%d",p1+i);
check(p1);
return 0;
}
void check(int *p)
{ int i;
printf("They are fail:");
for(i=0;i<5;i++)
if (p[i]<60)
printf("%d ",p[i]);
printf("\n");
}