起首,什么是函数?函数(function)是完成特定职责的独立步骤代码单位。语法例则界说了函数的布局和使用办法。固然C中的函数和其他言语中的函数、子步骤、历程作用相反,但是细节上略有不同。一些函数实行某些举措,如printf()把数据打印到屏幕上;一些函数找出一个值供步骤使用,如strlen()把指定字符串的长度前往给步骤。寻常而言,函数可以同时具有以上两种功效。
为什么要使用函数?起首,使用函数可以省去编写反复代码的苦差。假如步骤要多次完成某项职责,那么只需编写一个切合的函数,就可以在必要时使用这个函数,大概在不同的步骤中使用该函数,就像很多步骤中使用putchar()一样。其次,即使步骤只完成某项职责一次,也值得使用函数。由于函数让步骤愈加模块化,从而提高了步骤代码的可读性,更便利终期修正、完满。比如,假定要编写一个步骤完成以下职责:
可以使用底下的步骤:
int main(void)
{
float list[SIZE];
readlist(list, SIZE);
sort(list, SIZE);
average(list, SIZE);
bargraph(list, SIZE);
return 0;
}
固然,还要编写4个函数readlist()、sort()、average()和bargraph()的完成细节。形貌性的函数名能清晰地表达函数的用处和构造布局。然后,单独计划和测试每个函数,直到函数都能正常完成职责。
假如这些函数够通用,还可以用于其他步骤。很多步骤员喜好把函数看作是依据传入信息(输入)及其天生的值或呼应的举措(输入)来界说的“黑盒”。假如不是本人编写函数,基本不必体贴黑盒的内里举动。比如,使用printf()时,只需晓得给该函数传入格式字符串或一些参数以及printf()天生的输入,无需了解printf()的内里代码。以这种办法对待函数有助于把注意力会合在步骤的全体计划,而不是函数的完成细节上。因此,在入手编写代码之前,仔细思索一下函数应该完成什么职责,以及函数和步骤全体的干系。
怎样了解函数?起主要晓得怎样准确地界说函数、怎样调用函数和怎样创建函数间的通讯。我们从一个简便的步骤示例开头,协助读者理清这些内容,然后再具体解说。
我们的第1个目标是创建一个在一行打印40个星号的函数,并在一个打印表头的步骤中使用该函数。如步骤清单9.1所示,该步骤由main()和starbar()构成。
/* lethead1.c */
void starbar(void); /* prototype the function */
int main(void)
{
starbar();
printf("%s\n", NAME);
printf("%s\n", ADDRESS);
printf("%s\n", PLACE);
starbar(); /* use the function */
return 0;
}
void starbar(void) /* define the function */
{
int count;
for (count = 1; count <= WIDTH; count++)
putchar('*');
putchar('n');
}
该步骤的输入如下:
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
该步骤要注意以下几点。
void starbar(void);
圆括号标明starbar是一个函数名。第1个void是函数典范,void典范标明函数没有前往值。第2个void(在圆括号中)标明该函数不带参数。分号标明这是在声明函数,不是界说函数。也就是说,这行声明白步骤将使用一个名为starbar()、没有前往值、没有参数的函数,并报告编译器在别处查找该函数的界说。关于不识别ANSI C作风原型的编译器,只需声明函数的典范,如下所示:
void starbar();
注意,一些老版本的编译器乃至连void都识别不了。假如使用这种编译器,就要把没有前往值的函数声明为int典范。固然,最好照旧换一个新的编译器。
- 寻常而言,函数原型指明白函数的前往值典范和函数承受的参数典范。这些信息称为该函数的署名(signature)。关于starbar()函数而言,其署名是该函数没有前往值,没有参数。
- 步骤把starbar()原型置于main()的前方。固然,也可以放在main()内里的声明变量处。放在哪个地点都可以。
- 在main()中,实行毕竟下的语句时调用了starbar()函数:
starbar();
这是调用void典范函数的一种情势。当盘算机实行到starbar();语句时,会找到该函数的界说并实行此中的内容。实行完starbar()中的代码后,盘算机前往主调函数(calling function)持续实行下一行(本例中,主调函数是main()),见图9.1(更确切地说,编译器把C步骤翻译成实行以上利用的机器言语代码)。
- 步骤中starbar()和main()的界讨情势相反。起首函数头包含函数典范、函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最初以右花括号完毕。注意,函数头中的starbar()后方没有分号,报告编译器这是界说starbar(),而不是调用函数或声明函数原型。
Structure of a simple function.
假如把starbar()看作是一个黑盒,那么它的举动是打印一行星号。不必给该函数提供任何输入,由于调用它不必要其他信息。并且,它没有前往值,以是也不给main()提供(或前往)任何信息。简而言之,starbar()不必要与主调函数通讯。接下去先容一个函数间必要通讯的例子。
上述步骤的输入中,假如笔墨能居中,信头会愈加雅观。可以经过在打印笔墨之前打印一定数目标空格来完成,这和打印一定数目标星号(starbar()函数)相似,只不外如今要打印的是一定数目标空格。固然这是两个职责,但是职责十分相似,与其分散为它们编写一个函数,不如写一个更通用的函数,可以在两种情况下使用。我们计划一个新的函数show_n_char()(体现一个字符n次)。唯一要改动的是使用内置的值来体现字符和反复的次数,show_n_char()将使用函数参数来转达这些值。
我们来具体分析。假定可用的空间是40个字符宽。调用show_n_char('*', 40)应该恰好打印一行40个星号,就像starbar()之前做的那样。第2行GIGATHINK, INT.的空格怎样处理?GIGATHINK, INT.是15个字符宽,以是第1个版本中,笔墨后方有25个空格。为了让笔墨居中,笔墨的左侧应该有12个空格,右侧有13个空格。因此,可以调用show_n_char(' ',12)。
show_n_char()与starbar()很相似,但是show_n_char()带有参数。从功效上看,前者不会添加换行符,尔后者会,由于show_n_char()要把空格和文本打印成一行。步骤清单9.2是修正后的版本。为重申参数的事情原理,步骤使用了不同的参数情势。
/* lethead2.c */
void show_n_char(char ch, int num);
int main(void)
{
int spaces;
show_n_char('*', WIDTH); /* using constants as arguments */
putchar('n');
show_n_char(SPACE, 12); /* using constants as arguments */
printf("%sn", NAME);
spaces = (WIDTH - strlen(ADDRESS)) / 2;
/* Let the program calculate */
/* how many spaces to skip */
show_n_char(SPACE, spaces);/* use a variable as argument */
printf("%sn", ADDRESS);
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
/* an expression as argument */
printf("%sn", PLACE);
show_n_char('*', WIDTH);
putchar('n');
return 0;
}
/* show_n_char() definition */
void show_n_char(char ch, int num)
{
int count;
for (count = 1; count <= num; count++)
putchar(ch);
}
该函数的运转后果如下:
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
底下我们回忆一下怎样编写一个带参数的函数,然后先容这种函数的用法。
函数界说从底下的ANSI C作风的函数头开头:
void show_n_char(char ch, int num)
该行见告编译器show_n_char()使用两个参数ch和num,ch是char典范,num是int典范。这两个变量被称为情势参数(formal-argument,但是迩来的标准保举使用formalparameter),简称形参。和界说在函数中变量一样,情势参数也是局部变量,属该函数公有。这意味着在其他函数中使用同名变量不会惹起称呼分歧。每次调用函数,就会给这些变量赋值。
注意,ANSI C要求在每个变量前都声明其典范。也就是说,不克不及像平凡变量声明那样使用同一典范的变量列表:
void dibs(int x, y, z) /* invalid function header */
void dubs(int x, int y, int z) /* valid function header */
ANSI C也承受ANSI C之前的情势,但是将其视为废弃不必的情势:
void show_n_char(ch, num)
char ch;
int num;
这里,圆括号中仅有参数名列表,而参数的典范在后方声明。注意,平凡的局部变量在左花括号之后声明,而外表的变量在函数左花括号之前声明。假如变量是同一典范,这种情势可以用逗号分开变量名列表,如下所示:
void dibs(x, y, z)
int x, y, z; /* valid */
如今的标准正渐渐镌汰ANSI之前的情势。读者应对此有所了解,以便能看懂从前编写的步骤,但是本人编写步骤时应使用如今的标准情势(C99和C11标准持续告诫这些过时的用法即将被镌汰)。固然show_n_char()承受来自main()的值,但是它没有前往值。因此,show_n_char()的典范是void。底下,我们来学习怎样使用函数。
在使用函数之前,要用ANSI C情势声明函数原型:
void show_n_char(char ch, int num);
当函数承受参数时,函数原型用逗号分开的列表指明参数的数目和典范。依据一局部喜好,你也可以省略变量名:
void show_n_char(char, int);
在原型中使用变量名并没有实践创建变量,char仅代表了一个char典范的变量,以此类推。
再次提示,ANSI C也承受已往的声明函数情势,即圆括号内没有参数列表:
void show_n_char();
这种情势终极会从标准中剔除。即使没有被剔除,如今函数原型的计划也更有上风(稍后会先容)。了解这种情势的写法是为了今后读得懂从前写的代码。
在函数调用中,实践参数(actual argument,简称实参)提供了ch和num的值。思索步骤清单9.2中第1次调用show_n_char():
show_n_char(SPACE, 12);
实践参数是空格字符和12。这两个值被赋给show_n_char()中相应的情势参数:变量ch和num。简而言之,情势参数是被调函数(called-function)中的变量,实践参数是主调函数(calling-function)赋给被调函数的具体值。如上例所示,实践参数可以是常量、变量,或乃至是更繁复的表达式。无论实践参数是何种情势都要被求值,然后该值被拷贝给被调函数相应的情势参数。以步骤清单9.2中最初一次调用show_n_char()为例:
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
构成该函数第2个实践参数的是一个很长的表达式,对该表达式求值为10。然后,10被赋给变量num。被调函数不晓得也不体贴传入的数值是来自常量、变量照旧寻常表达式。再次重申,实践参数是具体的值,该值要被赋给作为情势参数的变量(见图9.3)。由于被调函数使用的值是从主调函数中拷贝而来,以是无论被调函数对拷贝数据举行什么利用,都不会影响主调函数中的原始数据。
Formal parameters and actual arguments
注意:
实践参数和情势参数实践参数是显如今函数调用圆括号中的表达式。情势参数是函数界说的函数头中声明的变量。调用函数时,创建了声明为情势参数的变量并初始化为实践参数的求值后果。步骤清单9.2中,'*'和WIDTH都是第1次调用show_n_char()时的实践参数,而SPACE和11是第2次调用show_n_char()时的实践参数。在函数界说中,ch和num都是该函数的情势参数。
从黑盒的视角看show_n_char(),待体现的字符和体现的次数是输入。实行后的后果是打印指定命量的字符。输入以参数的情势被转达给函数。这些信息清晰地标明白如安在main()中使用该函数。并且,这也可以作为编写该函数的计划分析。
黑盒办法的中心局部是:ch、num和count都是show_n_char()公有的局部变量。假如在main()中使用同名变量,那么它们互相独立,互不影响。
也就是说,假如main()有一个count变量,那么改动它的值不会改动show_n_char()中的count,反之亦然。黑盒里产生了什么对主调函数是不偏见的。
前方先容了怎样把信息从主调函数转达给被调函数。反过去,函数的前往值可以把信息从被调函数传回主调函数。为进一步分析,我们将创建一个前往两个参数中较小值的函数。由于函数被计划用来处理int典范的值,以是被定名为imin()。别的,还要创建一个简便的main(),用于反省imin()对否正常事情。这种被计划用于测试函数的步骤偶尔被称为驱动步骤(driver),该驱动步骤调用一个函数。假如函数告捷经过了测试,就可以安装在一个更紧张的步骤中使用。步骤清单9.3演示了这个驱动步骤和前往最小值的函数。
/* lesser.c -- finds the lesser of two evils */
int imin(int, int);
int main(void)
{
int evil1, evil2;
printf("Enter a pair of integers (q to quit):\n");
while (scanf("%d %d", &evil1, &evil2) == 2)
{
printf("The lesser of %d and %d is %d.\n",
evil1, evil2, imin(evil1,evil2));
printf("Enter a pair of integers (q to quit):\n");
}
printf("Bye.\n");
return 0;
}
int imin(int n,int m)
{
int min;
if (n < m)
min = n;
else
min = m;
return min;
}
追念一下,scanf()前往告捷读取数据的个数,以是假如输入不是两个整数会招致循环停止。底下是一个运转示例:
Enter a pair of integers (q to quit):
509 333
The lesser of 509 and 333 is 333.
Enter a pair of integers (q to quit):
-9393 6
The lesser of -9393 and 6 is -9393.
Enter a pair of integers (q to quit):
q
Bye.
紧张字return后方的表达式的值就是函数的前往值。在该例中,该函数前往的值就是变量min的值。由于min是int典范的变量,以是imin()函数的典范也是int。
变量min属于imin()函数公有,但是return语句把min的值传回了主调函数。底下这条语句的作用是把min的值赋给lesser:
lesser = imin(n,m);
对否写成底下如此:
imin(n,m);
lesser = min;
不克不及。由于主调函数乃至不晓得min的存在。记取,imin()中的变量是imin()的局部变量。函数调用imin(evil1, evil2)只是把两个变量的值拷贝了一份。
前往值不仅可以赋给变量,也可以被用作表达式的一局部。比如,可以如此:
answer = 2 * imin(z, zstar) + 25;
printf("%dn", imin(-32 + answer, LIMIT));
前往值不一定是变量的值,也可以是随意表达式的值。比如,可以用以下的代码简化步骤示例:
/* minimum value function, second version */
imin(int n,int m)
{
return (n < m) ? n : m;
}
条件表达式的值是n和m中的较小者,该值要被前往给主调函数。固然这里不要求用圆括号把前往值括起来,但是假如想让步骤条理更清晰或一致作风,可以把前往值放在圆括号内。
假如函数前往值的典范与函数声明的典范不婚配会怎样?
int what_if(int n)
{
double z = 100.0 / (double) n;
return z; // what happens?
}
实践取得的前往值相当于把函数中指定的前往值赋给与函数典范相反的变量所取得的值。因此在本例中,相当于把z的值赋给int典范的变量,然后前往int典范变量的值。比如,假定有底下的函数调用:
result = what_if(64);
固然在what_if()函数中赋给z的值是1.5625,但是return语句前往int典范的值1。
使用return语句的另一个作用是,停止函数并把控制前往给主调函数的下一条语句。因此,可以如此编写imin():
/* minimum value function, third version */
imin(int n,int m)
{
if (n < m)
return n;
else
return m;
}
很多C步骤员都以为只在函数末了使用一次return语句比力好,由于如此做更便利欣赏步骤的人了解函数的控制流。但是,在函数中使用多个return语句也没有错。无论怎样,对用户而言,这3个版本的函数用起来都一样,由于一切的输入和输入都完全相反,不同的是函数内里的完成细节。底下的版本也没成绩:
/* minimum value function, fourth version */
imin(int n, int m)
{
if (n < m)
return n;
else
return m;
printf("Professor Fleppard is like totally a fopdoodle.n");
}
return语句招致printf()语句永久不会被实行。假如Fleppard传授在本人的步骤中使用这个版本的函数,约莫永久不晓得编写这个函数的学生对他的看法。别的,还可以如此使用return:
return;
这条语句会招致停止函数,并把控制前往给主调函数。由于return后方没有任何表达式,以是没有前往值,仅有在void函数中才会用到这种情势。
声明函数时必需声明函数的典范。带前往值的函数典范应该与其前往值典范相反,而没有前往值的函数应声明为void典范。假如没有声明函数的典范,旧版本的C编译器会假定函数的典范是int。这一常规源于C的早前,当时的函数绝大大多都是int典范。但是,C99标准不再支持int典范函数的这种假定设置。
典范声明是函数界说的一局部。要记取,函数典范指的是前往值的典范,不是函数参数的典范。比如,底下的函数头界说了一个带两个int典范参数的函数,但是其前往值是double典范。
double klink(int a, int b)
要准确地使用函数,步骤在第1次使用函数之前必需晓得函数的典范。办法之一是,把完备的函数界说放在第1次调用函数的前方。但是,这种办法增长了步骤的阅读难度。并且,要使用的函数约莫在C库或其他文件中。因此,通常的做法是事先声明函数,把函数的信息见告编译器。
int imin(int, int);
int main(void)
{
int evil1, evil2, lesser;
第2行代码分析imin是一个函数名,有两个int典范的形参,且前往int典范的值。如今,编译器在步骤中调用imin()函数时就晓得应该怎样处理。
我们把函数的前置声明放在主调函数外表。固然,也可以放在主调函数内里。比如,重写lesser.c 的开头局部:
int main(void)
{
int imin(int, int); /* imin() declaration */
int evil1, evil2, lesser;
注意在这两种情况中,函数原型都声明在使用函数之前。
ANSI-C标准库中,函数被分红多个系列,每一系列都有各自的头文件。这些头文件中除了其他内容,还包含了本系列一切函数的声明。比如,stdio.h头文件包含了标准I/O库函数(如,printf()和scanf())的声明。math.h头文件包含了种种数学函数的声明。比如,底下的声明:
double sqrt(double);
见告编译器sqrt()函数有一个double典范的形参,并且前往double典范的值。不要殽杂函数的声明和界说。函数声明见告编译器函数的典范,而函数界说则提供实践的代码。在步骤中包含math.h头文件见告编译器:sqrt()前往double典范,但是sqrt()函数的代码在另一个库函数的文件中。
版权声明:本文来自互联网整理发布,如有侵权,联系删除
原文链接:https://www.yigezhs.comhttps://www.yigezhs.com/qingganjiaoliu/36245.html