内容正文:
第10章 指针的高级应用
内容概述
指向多维数组的指针
指向函数的指针
返回指针值的函数
指针数组
指向指针的指针
复习数组的指针的含义,学习通过指针引用多维数组的元素,了解指向数组的指针作为函数参数的传递过程 ;
了解函数指针的概念,正确地利用指针调用函数;
掌握返回指针的函数的定义和调用;
初步认识指向指针的指针,进而理解指针数组的应用;
教学目标
10.1 指针与函数
正确理解一个函数应从以下几个方面考虑:
函数参数(形参和实参)
代表函数实体的函数名
函数返回值类型
理解函数与指针的关系也要从这几个方面考虑:
指针作为函数的参数
函数的返回值是指针
指向函数的指针。
10.1.1 指针型函数
C语言中允许一个函数的返回值是一个指针(地址),这种返回指针值的函数称为指针型函数。
定义形式
类型说明符 *函数名([参数列表])
说明:
函数名之前加*号表明这是指针型函数,即返回值是指针。
类型说明符表示了返回的指针值所指向的数据类型。
举例
int *fun(int a, int b)
{
/* 函数体 */
}
函数fun()是一个指针型函数;
返回值是int*类型。
【例10.1】编写指针型函数找出两个整型数中的最大值。
#include <stdio.h>
int *max(int *x, int *y)
{
return (*x>*y?x:y); /*返回最大数的指针*/
}
main()
{
int a,b,*p;
scanf("%d, %d",&a,&b);
p=max(&a,&b);
printf("max=%d", *p);
}
10.1.2 指向函数的指针
函数代码是程序的算法指令部分,它们同样占据内存空间,存放在代码(code)区。C语言规定,函数名就表示该函数所占内存区域的首地址,或称该函数的入口地址,将函数首地址赋给某一指针变量,则通过该指针变量就可以调用这个函数。这种指向函数的指针变量称为函数指针变量,简称函数指针。
定义形式
类型 (*变量名)([参数类型列表]);
说明
“类型”表示指针所指函数的返回值的类型;
“(*指针变量名)”表示“*”后面定义的变量是指针变量;
最后的括号表示指针变量所指的是一个函数,
参数类型列表列出函数参数的类型
指向函数的指针变量也称函数指针变量
定义函数指针变量是为了方便对函数的调用。函数指针变量不是固定指向哪一个函数的,在程序中,哪个函数的地址赋予它,它就指向哪个函数。
用函数指针变量调用函数的一般形式为:
(*指针变量名)(实参表) 或
指针变量名(实参表)
例如:
int (*pf)();
表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
【例10.2】利用函数指针编程实现:输入三角形的两直角边长,求其面积和斜边长。
#include <stdio.h>
#include <math.h>
float area(int a, int b) /* 求三角形的面积 */
{
return (float)a*b/2;
}
float length(int a,int b) /* 求三角形的斜边长 */
{
return sqrt(a*a + b*b);
}
main()
{
int m,n;
float s,l;
float (*f)(int,int);
scanf("%d%d",&m,&n);
f=area; s=(*f)(m,n);
f=length; l=(*f)(m,n); length()*/
printf("area=%.2f,length=%.2f
",s,l);
}
(1)与其他指针变量相同,指向函数的指针变量在使用前也必须进行初始化操作,具体形式为“指针变量=函数名”。不要写成“指针变量=函数名(形式参数)”的形式。若带参数,它就不再是一个地址,而是函数调用,返回的是整型数。例如,
指针赋值: f=area;
函数调用: s=area(m,n);
说明:
(2)函数的调用可以通过函数名调用,也可以通过函数指针调用,只需将(*函数指针变量)代替函数名即可。
例如,
s=area(m,n); 相当于 s=(*f)(m,n);
l=length(m,n); 相当于“l=(*f)(m,n);
(3)指向函数的指针变量可以先后指向不同的函数,但这些函数的返回值类型应相同,且与函数指针定义中的类型标识符相同。例如
f=area; 和 f=length;
(4)函数指针变量不能进行算术运算,即函数指针的移动是毫无意义的。
10.2 用指针访问二维数组
10.2.1 二维数组元素的地址
设: char a[3][4] = {{‘1’, ‘2’,’ 3’,’ 4’}, { ‘5’, ‘6’,’7’,’ 8’}, {‘9’, ’10’, ’11’, ’12’}};
它可以这样理解:
数组a有三个元素a[0]、a[1]、a[2] ;
每一个元素代表一行;
每行是一个包含4个元素的一维数组。
第一行
第二行
第三行
a[0]
a[1]
a[2]
a
a+1
a+2
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
10.2.2 指向二维数组元素的指针
这种方法相当于把二维数组转化为一维数组来使用。当用一个指针变量指向第1行的首地址后,即可搜索到全部元素。
设二维数组的大小为m*n, 则第i, j个元素相对于a[0][0]的个数为i n + j (i=0,1,…,n–1)
设: int p;
p=a[0];
则 a[i][j] 的地址为 p+i n+j
指针p
数组a
a[i][j]的地址:p+in+j
【例10.3】用指针变量输出数组元素及直接用下标输出数组元素的比较。
#include <stdio.h>
main()
{ int a[3][4]={{1,3,6,9},{7,12,24,17},{11,31,25,30}};
int *p, i, j;
for(i=0,p=&a[0][0];i<12;i++)
printf("%4d",*p++); /* 指针表示法 */
putchar('
');
for(i=0;i<3; i++)
for(j=0;j<4;j++)
printf("%4d",a[i][j]); /* 数组下标表示法 */
putchar('
');
}
10.2.3 指向二维数组中一行元素的指针
可以定义一个指针变量p,指针p的移动是以行为单位的,即让p指向数组a的第0行,则p+1指向数组a的第1行。
指向行的指针变量的说明形式为
数据类型(*指针名)[N];
例如,
int a[3][4];
int (*p)[4];
于是引用第(i, j)个元素的方法:
((p+i)+j)
第i行首地址,相当于a[i]
a[0]
a[1]
a[2]
a
a+1
a+2
char a[3][4];
*a
*(a+1)
*(a+2)
a是一个长度为3的数组
数组元素是长度为4的数组
a、a+1、a+2都是指针,它们的基类型是长度为4的字符数组,它们与下面定义的指针p同类型
char (*p)[4];
a
a+1
a+2
a[0]
a[1]
a[2]
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
*a
a[0]
*a+1
a[0]+1
*a+2
a[0]+2
*a+3
a[0]+3
a[2]
*(a+2)
a[2]+1
*(a+2)+1
a[1]+3
*(a+1)+3
a[1][3]
*(*(a+1)+3)
a[1]
*(a+1)
a[2][3]
*(*(a+2)+3)
a[0][3]
*(*a+3)
char *
char
char *
基类型为
char[4]
的指针
【例10.4】利用指针变量输出二维数组的值。
#include "stdio.h"
main()
{ int a[3][4]={1,3,6,9,7,12,24,17,11,31,25,30},i;
int (*p)[4]=a;
for(p=a;p<a+3;p++)
{
for(i=0;i<4;i++)
printf("%3d",*(*p+i));
printf("
");
}
}
表示二维数组
a:指向二维数组的指针类型
表示第i行
a[i]、*(a+i):指向一维数组的指针类型
表示第i行j列的元素
a[i][j]、 *(*(a+i)+j)
*(a[i]+j)、(*(a+i))[j]:char类型
注意a和*a都是指针,但是基类型不同
注意*(a+i)和*a+i的区别
指向二维数组的指针总结
指向数组元素的指针变量
指向二维数组的元素
类型为 char *p;
根据一维数组元素和二维数组元素的对应关系,可以访问所有的二维数组元素
基类型为一维数组的指针变量
指向二维数组的行
类型为 char (*p)[4];
把每一行作为一个一维数组来处理
一维数组与二维数组
char a[M][N]; ↔ char a[M*N];
a[i][j] ↔ a[i*N+j]
使用指向元素的指针访问二维数组元素
char a[M][N];
char *p=a[0]; /* p=*a; */
则 p[i*N+j]、*(p+i*N+j)、a[i][j]
表示二维数组第i行j列的元素
二维数组是基类型为一维数组的指针
可以使用与二维数组同类型的指针变量
使用指向行的指针访问二维数组元素
int a[M][N];
int (*p)[N]=a; /* p=a; */
则p[i]、*(p+i)、a[i]表示数组的第i行
且p[i][j]、*(*(p+i)+j)、*(p[i]+j)、
(*(p+i))[j]表示二维数组第i行j列的元素
10.2.4 指针数组
一个数组的元素值为指针则这个数组称为是指针数组。指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。
指针数组的定义形式为
类型说明符 数组名[常量];
其中类型说明符为指针所指向的变量的类型。
例如:int *p[3];
表示p是一个指针数组,它有三个数组元素, 每个元素值都是一个指针,指向整型变量。
注意与基类型为数组的指针区分
char (*p)[4];
【例10.5】将字符串"China"、"India"、"Japan"、"America"、"Canada"按字母顺序(由小到大)输出。
Canada
America
Japan
India
China
name[4]
name[3]
name[2]
name[1]
name[0]
Canada
America
Japan
India
China
name[4]
name[3]
name[2]
name[1]
name[0]
#include "stdio.h"
#include "string.h"
main()
{
void sort(char *name[],int n);
char *name[]={"China","India","Japan","America","Canada"};
int i,n=5;
sort(name,n);
for(i=0;i<n-1;i++)
printf("%s
",name[i]);
}
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;
}
}
}
10.2.5 二维数组名做函数参数
与一维数组名一样,二维数组名也可以作函数参数。但它们的基类型不同。二维数组的基类型是一维数组,它本质上是一个指向行的指针。
例如
void foo(int *p, int n); /* 函数定义 */
void bar(int (*p)[4], int n); /* 函数定义 */
int a[3][4];
......
foo(*a, 12); /* 函数调用 */
bar(a, 3); /* 函数调用 */
【例10.6】从键盘读取3行字符(每行字符少于80个),存入到字符串数组a中,要求按行将字符串数组a中出现的大写字符转换为小写字符。
〖程序分析〗
这里定义一个函数void CharConva(char xx[3][80]),其功能是将二维数组xx中的大写字符转换成小写字符。参数xx实质上是一个行指针,它的定义也可以写成char (*xx)[80]。当主函数调用CharConva()函数时,xx指向主函数中二维数组a的第一行。在CharConva()函数中对每一行进行处理时,定义一个指向字符的指针,对字符串的一个个字符进行处理。
#include<stdio.h>
#include<string.h>
void CharConva(char xx[3][80])
{ int i;
char *p;
for(i=0;i<3;i++)
{
p=xx[i]; /*指针p指向第i行的首字符*/
for(p=xx[i];*p!=‘\0’;p++) /* 处理第i行字符串 */
if((*p>='A')&&(*p<='Z'))
*p=*p+32;
}
}
void main()
{
char a[3][80];
int i;
printf("Input three strings:
");
for(i=0;i<5;i++)
gets(a[i]);
CharConva(a);
printf("After converting:
");
for(i=0;i<3;i++)
puts(a[i]);
}
$$