如何理解和应用C语言中的复杂声明
曹定成
(华中科技大学计算机科学与技术学院,湖北武汉430074)
摘要:讨论了正确理解复杂声明的含义以及正确使用复杂声明所说明的对象的方法。讨论了各种基本类型指针,构成复杂声明的基本元素,以及类型说明符和数据类型名的优先级和结合性。并以复杂声明char((f(char(para)(char)))[2])();为例讨论了解释复杂声明的步骤和方法,给出了解释结果,并且给出了使用复杂声明的应用实例。
关键词: 复杂声明;C语言;指针
中图法分类号:TP312文献表示码:A
HowtoUnderstandingandUsingtheComplicated
DeclarationsinC
DingchengCao
(CollegeofComputerSci.&Tech.HuazhongUniversityofScienceandTechnology, Wuhan430074,China)
Abstract:Inthispaper,amethodofcorrectlyunderstandingthemeaningofacomplicateddeclarationandusingtheobjectinacomplicateddeclarationarediscussed.Variousprimarypointers,thebasicelementsincomplicateddeclaration,andtheprecedenceandtheleft-associativityoftheoperatorsandthetypespecifiersarealsodiscussed.Accordingtothecomplicateddeclarationchar((f(char(para)(char)))[2])();theapproachandthemethodofinterpretingcomplicateddeclarationareintroduced.Meanwhile,theapplicationexampleofusingcomplicateddeclarationisputforward.
Keywords:complicateddeclarations;C;pointer
1引言
指针是C语言中的一个重要概念,也是C语言的一个重要特色[1]。以指针为基础的复杂声明是C语言中的重要组成部分。对于形如char((f(char(para)(char)))[2])();的复杂声明语句,许多C语言的学习者都感到f的含义非常难以理解,更不敢问津f的使用。的确,复杂声明是学习C语言的难点。但是,准确理解复杂声明的含义也是提高C程序员素质的重要途径。B.W.Kernighan和D.M.Ritchie曾经说过:“毫无疑问,复杂声明的确产生于实践,但知道如何去理解它们以及在必要时知道如何去创建它们却是非常重要的[2]。”可以这样说:对复杂声明的精确理解,以及创建、使用和驾驭复杂声明的熟练程度是衡量一个C程序员C语言素质高低的重要指标之一。
如何才能正确理解复杂声明的含义?如何在理解复杂声明含义的基础上正确使用复杂声明所说明的对象?其方法是:首先,要对基本类型指针的理解打下扎实的基础;其次,在复杂声明的解释过程中要严格按照优先级和结合性来进行;然后才是在理解的基础上使用复杂声明。
2基本类型指针
在C语言中,称存储某个变量a的地址值的变量p为指针变量。当指针变量p存储变量a的地址时,称p指向a。在不产生二义情况下,往往称指针变量为指针。指针类型是派生类型。根据指针所指变量是字符型、整型、或浮点型变量,将指针称为字符指针、整型指针、或浮点指针。如:intx=1,pi=&x;就说明了pi是指向整型变量x的整型指针。
数组也可以用指针表示。如:inta[5],pa=a;它使指针pa指向一维数组元素a[0],pa是指向数组元素的指针。如果用指针表示多维数组,则需要用到指向数组的指针。如:intb[2][3],(pb)[3]=b;此时pb是指向有三个元素的一维数组的指针,它指向b数组中行下标为0的行。那么,pa++和pb++有什么区别呢?pa++使pa指向a[1],pa的值增加sizeof(int);而pb++使pb指向b数组中行下标为1的行,pb的值增加3sizeof(int)。
由同类型指针可以构成指针数组。如:intp[3];说明p是有三个元素的整型指针数组。即:指针数组p中的每个元素都是一个整型指针。因此,如果有声明语句:intx=2,y=2,z=3;则:p[0]=&x;p[1]=&y;p[2]=&z;就使指针数组p中的元素分别指向整型变量x、y、z。
如果一个指针以某个函数的入口地址为其值,则该指针称为指向函数的指针,简称为函数指针。如:int(pf)(char,int);说明pf是一个函数指针,它所指向的函数有一个字符指针和一个整型指针为形参,且返回值为整型值。如果有声明语句:intfun(chara,intb);并且fun有定义,则pf=fun就使函数指针pf指向了函数fun。设pc,pi是已赋值的字符指针和整型指针,则通过(pf)(pc,pi)或pf(pc,pi)就可以调用函数fun。
如果函数的返回值是指针类型的值,该函数就称为指针函数[3]。所以,指针函数是返回值为指针值的函数。如:intfp(inta);就说明fp是一个整型指针函数,它返回一个整型指针值。通过指针函数,可以使函数间接返回可以被调用函数处理的多个值。
字符指针、整型指针、浮点指针、指向数组元素的指针、指向数组的指针、指针数组、函数指针和指针函数都被称为基本类型的指针。
3理解复杂类型的关键
构成复杂声明的基本元素是类型说明符和数据类型名。理解复杂类型的关键是理解并正确运用类型说明符和数据类型名在解释过程中的优先级和结合性。
复杂声明中的类型说明符有:()、[]、三种。其中()用于说明函数类型以及改变说明的先后顺序;[]用于说明数组类型;则用于说明指针类型。数据类型名可以是int、char、float、double、void等基本类型名,也可以是用户自定义的构造类型。
在解释复杂声明时,要按照()、[]优先级最高,次之,而数据类型名的优先级最低的顺序来解释。当()和[]同时在复杂声明中出现时,就需要考虑类型说明符的结合性。C语言规定,类型说明符()和[]的结合性是左结合,即按从左至右的顺序进行解释。
在具体的解释过程中,对复杂声明要按照优先级和结合性进行解释顺序的分解,确定解释的先后顺序,然后进行逐步分析,在每一步分析过程中要运用基本类型指针涉及的知识进行解释,最后归纳综合出复杂声明的含义。
以复杂声明char((f(char(para)(char)))[2])();为例。首先按照类型说明符和数据类型名的优先级和结合性对其进行分解得到:
f((char()(charpara))(([2]((()((char
①②③④⑤⑥⑦
①f与(char(para)(char))作用后可以得出:f是一个函数。char(para)(char)是f函数的形参说明。它的解释顺序是:para(((char)((char。即f函数的形参para是一个函数指针,所指向的函数有一个字符指针的形参,并且返回值是字符指针值。
②与作用后得出:f是一个指针函数。即f函数的返回值是一个指针值。
③与[3]作用后得出:f是一个指针函数,其返回值是指向有两个元素数组的指针。
④与作用后得出:f是一个指针函数,其返回值是指向有两个元素的指针数组的指针。
⑤与()作用后得出:f是一个指针函数,其返回值是指向有两个元素的函数指针数组的指针,函数指针数组中的指针元素所指函数无参。
⑥与作用后得出:f是一个指针函数,其返回值是指向有两个元素的函数指针数组的指针,函数指针数组中的指针元素所指函数无参,其返回值为指针值。
⑦与char作用后得出:f是一个指针函数,其返回值是指向有两个元素的函数指针数组的指针,函数指针数组中的指针元素所指函数无参,其返回值为字符指针值。
所以,上面的复杂说明说明f是一个指针函数;其形参para是一个函数指针,para所指向的函数有一个字符指针的形参,返回值是字符指针值;并且,f函数的返回值是指向有两个元素的函数指针数组的指针,函数指针数组中的指针元素所指函数无参,其返回值为字符指针值。其物理含义可以由图1表示。
[0][0] [0][1] … [2][1]
f(char(para)(char));
图1复杂声明char((f(char(para)(char)))[2])();物理含义的图示
4复杂类型的应用
要掌握复杂声明,就需要在理解复杂声明的基础上进一步使用复杂声明。它可以进一步反过来强化对复杂声明的理解。仍以char((f(char(para)(char)))[2])();为例,讨论如何使用f。首先要构造f函数的形参,其次要构造f函数的返回值。
f函数的形参是一个指向有字符指针形参的字符指针函数的函数指针。因此在main函数中通过char(pf)(char);声明了pf为函数指针;同时,通过声明charfun(chars);和pf=fun;使函数指针pf指向函数fun。这样,通过f(pf)就可以调用f函数。
由于f函数的返回值是指向有两个元素的函数指针数组的指针,因此需要声明一个二维函数指针数组a,在f函数中通过char((pa)[2])();声明pa是指向有两个元素的函数指针数组的指针,通过pa=a就使pa指向了二维函数指针数组a。
同时,在f函数中通过pa向二维函数指针数组a的各个元素进行赋值。从a[0][0]开始,到a[2][1]为止,各个元素依次指向函数f00到f21。通过returnpa;将指向二维函数指针数组a的指针值返回。
与此同时,在main函数中通过p=f(pf);使p指向二维函数指针数组a。在双重循环中通过(p[i][j])()依次调用函数f00、f01、…、f20、f21。也可以用p[i][j]()的形式来进行调用。
相应的程序如下:
#include"stdio.h"
charfun(chars);/fun是有字符指针形参的字符指针函数/
char((f(char(para)(char)))[2])();
charf00();charf01();/f00到f21都是无参字符指针函数/
charf10();charf11();
charf20();charf21();
char(a[3][2])();/声明二维函数指针数组a/
voidmain(void)
{
char((p)[2])();/p是指向有两个元素的函数指针数组的指针/
char(pf)(char);/pf是指向有字符指针形参的字符指针函数的函数指针/
inti,j;
pf=fun;/对pf赋值,使pf指向函数fun/
p=f(pf);/调用f函数,返回指向二维函数指针数组指针值赋给指针p/
for(i=0;i<3;i++)
for(j=0;j<2;j++)
printf("%s\n",(p[i][j])());/通过指针p调用f00、…、f21/
}
charf00()/定义函数f00/
{
staticchars00[]="functionf00iscalled!";
returns00;
}
charf01()/定义函数f01/
{
staticchars01[]="functionf01iscalled!";
returns01;
}
charf10()/定义函数f10/
{
staticchars10[]="functionf10iscalled!";
returns10;
}
charf11()/定义函数f11/
{
staticchars11[]="functionf11iscalled!";
returns11;
}
charf20()/定义函数f20/
{
staticchars20[]="functionf20iscalled!";
returns20;
}
charf21()/定义函数f21/
{
staticchars21[]="functionf21iscalled!";
returns21;
}
char((f(char(para)(char)))[2])()/定义函数f/
{
char((pa)[2])();/声明指向有两个元素的函数指针数组的指针pa/
charpret;
pa=a;/使pa指向二维函数指针数组a/
pret=(para)("thisisatest!");/调用para所指函数,返回值赋给pret/
printf("infunctionf,pretis%s\n",pret);/输出pret所指对象/
pa[0][0]=f00;pa[0][1]=f01;/对二维函数指针数组的元素进行赋值/
pa[1][0]=f10;pa[1][1]=f11;
pa[2][0]=f20;pa[2][1]=f21;
returnpa;/返回指向二维函数指针数组的指针/
}
charfun(chars)/定义有字符指针形参的字符指针函数fun/
{
printf("infunctionfun,sis%s\n",s);
returns;
}
程序的运行结果如下:
infunctionfun,sisthisisatest!
infunctionf,pretisthisisatest!
functionf00iscalled!
functionf01iscalled!
functionf10iscalled!
functionf11iscalled!
functionf20iscalled!
functionf21iscalled!
5结论
复杂声明之所以感到难以理解,主要是声明所描述的数据类型比较复杂,比较抽象。只要把基本类型指针的基础打扎实,牢牢把握住复杂声明语句中类型说明符和数据类型名的优先级和结合性,正确分解出解释顺序,然后一步一步的进行解释,就可以得到概念清晰的解释结果。如果进一步使用复杂声明语句中声明的对象,则可以大大增强对复杂声明理解的深度,提高使用C语言的能力和素养。
参考文献
[1]阳小兰,钱程.C语言指针解析.软件导刊.W.Kernighan,D.M.Ritchie.THECPROGRAMMINGLANGUAGE(SECONDEDITION).清华大学出版社·PRENTICEHALL,2001.6p122
[3]秦友淑,曹化工.C语言程序设计教程(第二版).华中科技大学出版社,2002.12,p230
5
charf00();
charf01();
charf21();
…
|
|