第10章指针的进一步讨论.docx
《第10章指针的进一步讨论.docx》由会员分享,可在线阅读,更多相关《第10章指针的进一步讨论.docx(35页珍藏版)》请在冰豆网上搜索。
![第10章指针的进一步讨论.docx](https://file1.bdocx.com/fileroot1/2023-2/25/c47c5aef-576f-4955-be72-5b6630b60b45/c47c5aef-576f-4955-be72-5b6630b60b451.gif)
第10章指针的进一步讨论
第10章指针的进一步讨论
在第8章中已经对指针进行了讨论,介绍了指针和指针变量的概念,指针变量的定义、初始化和运算,一维数组和二维数组的指针,字符指针与字符串,以及指针作为函数参数等。
在第9章中又介绍了结构体类型数据的指针及其用法。
在C语言中,指针的用途非常广泛且用法非常灵活,本章将对指针进行进一步的讨论,主要介绍指针数组和指向指针的指针、函数的指针和指向函数的指针变量、返回指针值的函数等概念及其应用,以及用指针处理线性链表。
10.1指针数组与多级指针
10.1.1指针数组的概念及其应用
1.指针数组的概念
指针数组是指每一个数组元素均用来存储一个指针值的数组,即指针数组中的每一个元素都是指针变量。
指针数组的定义形式为:
类型标识符*数组名[数组长度];
例如,语句:
int*p[5];
定义了一个指针数组,数组名为p,共有5个元素,每个元素都是一个可以指向int型存储单元(或变量)的指针变量。
注意它与第8章已介绍的指向一维数组的指针变量的定义形式上的区别。
这里,运算符[]的优先级高于*,因此,p先与[5]结合,形成数组p[5],然后p[5]再与*结合,*表示此数组是指针数组。
指针数组的每一个元素(均为指针变量)的共同数据类型是“int*”。
对于如下语句:
inta[4][5];
定义的二维数组,我们知道,a[i](0≤i<4)均是指针,它指向二维数组元素a[i][0]。
因此我们可以用a[0]、a[1]、…、a[3]来初始化指针数组p,语句如下:
int*p[4]={a[0],a[1],a[2],a[3]};
这样,指针数组p的第i个元素p[i](它与*(p+i)等价)就指向二维数组a的第i行的第0个元素a[i][0],p[i]+j则指向a[i][j],因此,a[i][j]与*(p[i]+j)、*(*(p+i)+j)等价。
如图10-1所示。
注意:
p[i]并不是指向二维数组a的第i行,而是指向第i行的第0个元素a[i][0]。
也就是说,p[i]仅是一个元素指针,而指向二维数组第i行的是一个行指针。
2.指针数组的应用
指针数组特别适合于用来处理指向若干个字符串的问题,它将使字符串的处理更加方便灵活。
例如,资料室有若干本书,每本书都有一个书名(可看成是一个字符串),我们可以采用二维字符数组来处理该资料室的图书资料管理。
定义二维字符数组并初始化如下:
图10-1指针数组的定义与初始化
charc[4][40]={TheCProgrammingLanguage,DatabaseDesign,
DatabaseSystemImplementation,SoftwareEngineering};
其存储形式如图10-2所示。
利用二维数组来处理的缺点是每一本书的书名所占字节数(即二维数组的列数)相等。
但实际上每一本书的书名(字符串)的长度是不相等的,二维数组的列数必须按最大字符串的长度来准备,造成许多内存单元的浪费。
不仅浪费内存单元,而且在诸如字符串的排序等问题的处理上也较慢,因为它要将整个字符串的内容进行交换。
上述问题如果采用指针数组来处理,许多麻烦可以迎刃而解。
首先我们可以定义一个字符指针数组book,然后让指针数组的每一个元素(均为指针变量)分别指向一个字符串,如图10-3所示。
在第8章中我们已经介绍了通过指向字符的指针变量来处理字符串的问题。
图10-3利用指针数组处理字符串
图10-2利用二维数组处理字符串
例10.1对所有图书按字母顺序(由小到大)排序输出书名。
#include
#include
voidmain(){
voidsort(char*p[],int),prn(char*p[],int);//声明被调用函数的原型
char*book[]={TheCProgrammingLanguage,DatabaseDesign,
DatabaseSystemImplementation,SoftwareEngineering};
sort(book,4);//实参book是指针数组名
prn(book,4);
}
voidsort(char*p[],intn){//按升序对指针数组p所指向的字符串序列排序
inti,j,minpost;
char*t;
for(i=0;iminpost=i;
for(j=i+1;jif(strcmp(p[j],p[minpost])<0)minpost=j;
if(minpost!
=i){//交换指针数组元素的指向
t=p[i];p[i]=p[minpost];p[minpost]=t;
}
}
}
voidprn(char*p[],intn){
inti;
for(i=0;iprintf(%s\n,p[i]);
}
运行结果为:
DatabaseDesign
DatabaseSystemImplementation
SoftwareEngineering
TheCProgrammingLanguage
其中,排序函数sort实现字符串序列的升序排序,排序过程中,并没有真正移动字符串的存储,仅仅是通过交换指向字符串的指针来实现排序,最后,按指针数组book中数组元素的顺序得到的字符串(即指针数组元素所指向的字符串)序列就是升序排序的。
10.1.2指针数组作main函数的形参
在以前的程序中,主函数main()都是不带参数的。
这是一般用户程序的特点。
但在有些情况下(例如用C语言编写系统命令),当程序开始执行时,希望通过命令行将某些参数传递给程序,以控制程序的执行,这时main函数就需要带参数,以便来接受命令行的参数。
命令行的一般形式为:
命令名参数1参数2…参数n
带参数的main函数的一般格式如下:
voidmain(intargc,char*argv[]){
…
}
按照约定,main函数可以带有两个名为argc和argv的参数。
其中,argc是int型参数,它接受的值是命令行参数的数目;argv是指向char型的指针数组参数,它的每个数组元素接受的值分别是指向命令行各参数字符串的指针。
例如,有如下C程序:
#include
voidmain(intargc,char*argv[]){
inti;
for(i=1;iprintf(%s,argv[i]);
printf(\n);
}
假设该C程序的可执行文件名为CProgram.exe,如果命令行为:
C>CProgramPleaseinputnumber:
则形参argc的值为4,形参数组argv的值如图10-4所示。
程序运行结果为:
Pleaseinputnumber:
图10-4main函数的形参指针数组的指向
10.1.3行指针数组
前面已经讨论了指针数组的概念。
在第8章中我们还讨论过指向一维数组的指针、指针变量,并分别简称为行指针、行指针变量。
因此,行指针数组是指每一个数组元素都是存储行指针(即指向一维数组的指针)的数组,即指针数组中的每一个元素都是行指针变量。
行指针数组的定义形式为:
类型标识符(*数组名[指针数组长度])[指向的数组长度];
例如,语句:
int(*p[4])[6];
定义了一个行指针数组,数组名为p,共有4个元素,每个元素都是一个可以指向包含6个元素的int型一维数组的指针变量,即行指针变量。
对于如下语句:
inta[4][6];
定义的二维数组,我们知道,a+i(0≤i<4)是行指针,它指向二维数组的第i行(包含6个元素),即指向一个包含6个元素的一维数组。
因此我们可以用a,a+1,…,a+3来初始化行指针数组p,语句如下:
int(*p[4])[6]={a,a+1,a+2,a+3};
这样,行指针数组p的第i个元素p[i]就指向二维数组a的第i行,即行指针数组元素p[i]中存储的是一个行指针。
根据行指针的特点可知,*p[i]是指向二维数组a的第i行第0列的元素指针,它指向a[i][0]元素,因此*p[i]+j则指向a[i][j],即a[i][j]与*(*p[i]+j)等价。
例10.2某班有4个学生,学5门课程,每个学生5门课程的成绩及总成绩已给出,要求将他们按总成绩由高到低排序后输出。
定义二维数组score[4][7]来存放4个学生的成绩信息,第0列用来存放学生编号,第1至第5列分别用来存放5门课程的成绩,第6列用来存放总成绩。
定义行指针数组p[4],并将p[i]赋值为score+i,即让p[i]指向二维数组score的第i行。
如图10-5所示。
图10-5行指针数组的定义与赋值
程序如下:
#include
voidmain(){
intscore[4][7]={{1,80,82,95,88,93,438},{2,86,54,80,95,57,372},
{3,80,70,56,88,93,387},{4,95,89,87,80,96,447}};inti,j,(*p[4])[7];
voidsort(int(*p[])[7],int);//声明被调用函数的原型
for(i=0;i<4;i++)
p[i]=score+i;//令行指针数组元素p[i]指向二维数组score的第i行
sort(p,4);//实参p是行指针数组名
printf(No.\tscore1\tscore2\tscore3\tscore4\tscore5\ttotal\n);
for(i=0;i<4;i++){
for(j=0;j<7;j++)
printf(%3d\t,*(*p[i]+j));
printf(\n);
}
}
voidsort(int(*p[])[7],intn){//对行指针数组p所指向的学生成绩表排序
inti,j,post;
int(*t)[7];
for(i=0;ipost=i;
for(j=i+1;jif(*(*p[j]+6)>*(*p[post]+6))//比较两个学生总成绩的大小
post=j;
if(post!
=i){//交换行指针数组元素的指向
t=p[i];p[i]=p[post];p[post]=t;}
}
}
运行结果如下:
No.score1score2score3score4score5total
49589878096447
18082958893438
38070568893387
28654809557372
其中,排序函数sort实现对学生成绩表按总成绩的降序排序,排序过程中,并没有真正移动学生成绩表的存储,仅仅是通过交换指向学生成绩的行指针来实现排序,最后,按行指针数组p中数组元素的顺序得到的学生成绩(即行指针数组元素所指向的学生成绩行)表就是按总成绩降序排序的。
10.1.4多级指针
1.指向指针的指针与指向行指针的指针
例10.1中定义了指针数组book,它的每一个元素book[i]是一个指向字符的指针变量;数组名book也是一个指针,它是指向book[0]的指针。
因此book是一个指向指针的指针。
假设有一个指向int型变量i的指针变量p,p的值是指针,即变量i的指针&i;指针变量p本身又有指针(即&p),它是指向指针的指针;我们再定义一个指针变量b来存放指向指针的指针&p,这样指针变量b就指向了指针变量p,称指针变量b为指向指针的指针变量。
如图10-6所示。
图10-6指向指针的指针、指针变量
指向指针的指针变量的定义形式如下:
类型标识符**变量名;
例如,语句:
int**b;
定义了一个指向指针的指针变量b。
b的类型为int**,*b的类型为int*,**b的类型为int。
指向行指针的指针变量的定义形式如下:
类型标识符(**变量名)[指向的数组长度];
例如,语句:
int(**b)[4];
定义了一个指向行指针的指针变量b。
它的值*b是一个行指针,即*b指向一个包含4个int型元素的一维数组;**b是一个指向int型存储单元的指针,即**b指向*b所指向一维数组的第0个元素;***b是一个int型数据,即***b就是*b所指向一维数组的第0个元素。
例10.3指向指针的指针变量和指向行指针的指针变量。
#include
voidmain(){
inta[3][4]={{11,12,13,14},{21,22,23,24},{31,32,33,34}};
int(*p[3])[4]={a,a+1,a+2};//定义行指针数组p
char*q[]={WANCX,SHUW,LUOSW,LIUXP};//定义指针数组q
voidprn1(char**,int),prn2(int(**)[4],int,int);//声明被调函数的原型
prn1(q,4);//实参q是指向指针的指针
prn2(p,3,4);//实参p是指向行指针的指针
}
voidprn1(char**b,intk){//形参b是指向指针的指针变量
inti;
for(i=0;iprintf(%c,**b++);//输出*b所指向字符串的第一个字符
printf(\n);
}
voidprn2(int(**b)[4],intm,intn){//形参b是指向行指针的指针变量
inti,j;
for(i=0;ifor(j=0;jprintf(%d,*(**b+j));
printf(\n);
}
}
执行结果如下:
WSLL
11121314
21222324
31323334
2.指向指针的指针数组与指向行指针的指针数组
如果数组中的每一个元素都是用来存储一个指向指针的指针,则该数组称为指向指针的指针数组,即每一个数组元素都是指向指针的指针变量。
指向指针的指针数组的定义形式为:
类型标识符**数组名[数组长度];
例如,语句:
int**b[5];
定义了一个指向指针的指针数组,数组名为b,共有5个元素,每个元素b[i](即*(b+i))都是一个指向指针的指针变量,它的值*b[i](即*(*(b+i)))是一个指向int型存储单元的指针,因此,**b[i](即**(*(b+i)))是一个int型数据。
指向行指针的指针数组的定义形式如下:
类型标识符(**变量名[数组长度])[指向的数组长度];
例如,语句:
int(**b[5])[4];
定义了一个指向行指针的指针数组,数组名为b,共有5个元素,每个元素b[i](即*(b+i))都是一个指向行指针的指针变量,它的值*b[i](即*(*(b+i)))是一个指向包含4个元素的一维数组的指针,因此,**b[i](即**(*(b+i)))是一个指向一维数组(它是*b[i]所指向的一维数组)的第0个元素的指针,***b[i](即***(*(b+i)))是一维数组(它是*b[i]所指向的一维数组)的第0个元素。
例10.4指向指针的指针数组。
#include
voidmain(){
char*p[]={WANCX,SHUW,LUOSW,LIUXP};//定义指针数组p
char**b[4];//定义指向指针的指针数组b
inti;
for(i=0;i<4;i++)
b[i]=p+i;//给指向指针的指针数组元素b[i]赋值
for(i=0;i<4;i++)
printf(%s\n,*b[i]);
printf(\n);
}
运行结果如下:
WANCX
SHUW
LUOSW
LIUXP
10.2返回指针的函数
10.2.1返回指针的函数
一个函数不仅可以带回一个整型值、字符值、实型值等,也可以带回一个指针型的值。
这种返回指针的函数头(即函数的说明部分)的一般定义形式如下:
类型标识符*函数名(形参列表)
例10.5返回指针的函数。
#include
voidmain(){
inta[10]={60,30,27,54,59,92,47,76,82,18},*max(int*),*p;
p=max(a);
printf(Max=%d\n,*p);
}
int*max(int*p){
inti,*q=p;
for(i=1;i<10;i++)
if(*(p+i)>*q)q=p+i;
returnq;
}
执行结果如下:
Max=92
函数max的功能是查找实参传过来的一维数组a中最大值的元素,并将指向该最大值元素的指针返回给主调函数,即返回一个指向int型存储单元的指针。
如图10-7所示。
函数调用结束后返回92存储单元的指针
图10-7返回指针的函数
注意:
函数所返回的指针必须是指向尚未释放的存储单元的指针,如全局变量、主调函数定义变量所分配的存储单元,或动态存储分配函数所分配的存储单元(见10.4.2小节)等。
如果函数所返回的指针是指向本函数所分配的存储单元,则可能得不到正确的结果。
例如,如果将例10.5中的max函数修改为如下形式,则main函数中的输出结果可能不正确,这是因为返回指针所指向的m变量在函数返回后被释放了。
int*max(int*p){
inti,m=*p;
for(i=1;i<10;i++)
if(*(p+i)>m)m=*(p+i);
return&m;
}
10.2.2返回行指针的函数
把返回指针的函数和二维数组行指针的概念结合起来使用,就可得到返回行指针的函数头(即函数的说明部分)的一般定义形式:
类型标识符(*函数名(形参列表))[数组长度]
例10.6二维数组score[4][7]存放了4个学生5门课程的成绩及总成绩,第0列存放学生编号,第1至5列存放5门课程的成绩,第6列存放总成绩。
要求调用函数max找出总成绩最高的学生,并返回指向该学生的行指针,然后在主函数中输出该学生的成绩。
程序如下:
#include
voidmain(){
intscore[4][7]={{1,80,82,95,88,93,438},{2,86,54,80,95,57,372},
{3,80,70,56,88,93,387},{4,95,89,87,80,96,447}};
intj,(*p)[7];
int(*max(int(*)[7],int))[7];//声明被调用函数的原型
p=max(score,4);//实参score是二维数组名,即行指针
printf(No.\tscore1\tscore2\tscore3\tscore4\tscore5\ttotal\n);
for(j=0;j<7;j++)
printf(%3d\t,*(*p+j));
printf(\n);
}
int(*max(int(*p)[7],intn))[7]{//指出总成绩最高的学生
inti,(*q)[7]=p;
for(i=1;iif(p[i][6]>(*q)[6])q=p+i;
returnq;//返回总成绩最高的学生的行指针
}
运行结果如下:
No.score1score2score3score4score5total
49589878096447
10.3函数的指针
程序在编译时,每一个函数都分配了一个入口地址,这个入口地址就称为函数的指针。
10.3.1指向函数的指针变量
指针变量不仅可以指向一般变量,也可以指向数组和结构体类型变量,还可以指向函数。
我们可以定义一个指针变量来指向函数。
指向函数的指针变量的一般定义形式如下:
类型标识符(*变量名)();
例如,语句:
int(*p)();
定义了一个指针变量p,指针变量p可以用来指向一个返回int型值的函数。
这里,*p两侧的括号不可少,(*p)代表p是一个指针变量,(*p)()则代表p是一个指向函数的指针变量。
指向函数的指针变量定义后,并没有明确表示指向哪一个函数。
将某个函数的指针(函数名就代表该函数的指针)赋给指向函数的指针变量后,该指针变量就指向该函数,以后就可以通过该指针变量来调用该函数。
通过指向函数的指针变量调用所指向函数的一般调用形式为:
(*指针变量名)(实参列表);
例如,有一返回int型值的函数max,则:
int(*p)();/*定义指向函数的指针变量p*/
p=max;/*使指针变量p指向函数max*/
z=(*p)(a,b);/*通过指针变量p调用函数max*/
等价于:
z=max(a,b);
例10.7用梯形法求定积分的近似值。
用梯形法求定积分的近似值的公式为:
其中,
为等分小区间数,
。
以下程序调用trap函数求定积分,被积函数分别是:
①fun1(x)=1+x+x*x,且a=0,b=2,n=2000。
②fun2(x)=5+2*x+3*x*x,且a=1,b=2,n=1000。
程序如下:
#include
#include
doublefun1(doublex){
return(1.0+x+x*x);
}
doublefun2(doublex){
return(5.0+2*x+3*x*x);
}
doubletrap(double(*p)(double),doublea,doubleb,doublen){
doublet,h;
inti;
t=0.5*((*p)(a)+(*p)(b));
h=fabs(b-a)/n;
for(i=1;i<=n-1;i++)
t=t+(*p)(a+i*h);
t=t*h;
return(t);
}
voidmain(){
doubley1,y2,(*p1)(double),(*p2)(double);
p1=fun1;
p2=fun2;
y1=