什么是函数

程序是由多个零件组合而成的,而函数就是这种“零件”的一个较小单位。

main函数和库函数

C语言程序中,main函数是必不可少的。程序运行的时候,会执行main函数的主题部分。main函数中使用了printf、scanf、puts等函数。由C语言提供的这些为数众多的函数称为库函数。

什么是函数

当然,我们也可以自己创建函数。而实际上,我们也必须亲自动手创建各种函数。下面我们来自己创建一个简单的函数。
创建一个函数,接收两个整数参数,返回较大整数的值。
printf函数和scanf函数等创建得比较好得函数,即使不知道其内容,只要了解使用方法,也可以轻松使用。

函数定义

首先来说下函数的创建方法,这里我们来定义一个名为max2的函数,如下所示:

这里的函数定义由多个部分构成。

函数头

该部分表示函数的名称和格式。虽然称为头函数,实际上说它是函数的“脸”可能更加的合适。

返回类型

函数返回的值——返回值的类型。该函数的情况下,返回的是两个int型数值中较大的一个,所以其类型是int。

函数名

函数的名称。从其他零件调用函数时,使用函数名。

形参声明

小括号括起来的部分,是用于接收辅助性提示的变量——形式参数的声明。

函数体

函数体是复合语句。仅仅在某个函数中使用的变量,原则上应在该函数中声明和使用,但是要注意不能声明和形参同名的变量,否则会发生变量名冲突。

函数调用

下面是使用函数的方法,代码如下:

#includeint Max(int n, int m){if (n>m){return n;}else{return m;}}int mian(){int n = 0;int m = 0;puts("请输入两个整数:");printf("整数1:");scanf("%d", &n);printf("整数2:");scanf("%d", &m);printf("较大的整数的值是%d\n", Max(n, m));return 0;}

该程序中定义了两个函数Max和mian。程序启动时会执行的时main函数。
运行结果:

  1. 函数的调用形式是在函数后面加上小括号,这个小括号称为函数调用运算符。使用函数调用运算符的表达式就称为函数调用表达式。
  2. 函数调用运算符括起来的是实参,当实参不止一个的时候,要是使用逗号将其分开。
  3. 进行函数调用后,程序的流程将一下子跳转到该函数处,main函数的执行就会中断,直到该函数调用完成之后才会继续执行main函数中的代码。
  4. 进行函数调用后,程序的流程会转到被调用函数处,这时,传递过来的实参值会被赋给函数接收的形参。
  5. 形参的初始化完成后,将执行函数体。在程序中遇到return 语句,或者执行到函数体最后的大括号是,就会从该函数跳到调用函数。


如上图所示,执行到return b; return语句执行结束后,程序就会返回到原来进行调用的地方,再次执行被中断的main函数。
函数调用运算符的总结如下图:

函数调用的时候传递的只是函数的参数的值,因此调用函数时使用的实参既可以时变量,也可以是常量。
例如:下面的函数调用,将输出变量n1和5中比较哪一个值大
Max(n1,5);
另外需要注意的是,实参和形参是完全不同的两个东西,因此不用担心实参和形参的名字一样的问题。
上面我还提到了return 语句,它的结构如下图所示;

函数返回的是“表达式“的值,不能返回两个以上的值。

三个数中的最大值

下面我来说下写求三个整数中的最大值的函数,函数接收的形参,以及函数内定义的变量,都是该函数自己的东西。在函数max3的形参a、b、c和main函数的变量a、b、c虽然名称相同,但是分别是不同的东西。结构如下:

代码如下:

#include//返回三个数中的最大值函数int max3(int a, int b, int c){int max = a;if (b>max){max = b;}if (c>max){max = c;}return max;}int main(){int a = 0;int b = 0;int c = 0;puts("请输入三个整数。");printf("整数1;");scanf("%d", &a);printf("整数2;");scanf("%d", &b);printf("整数3;");scanf("%d", &c);printf("最大值是:%d", max3(a, b, c, ));return 0;}

将函数的返回值作为参数传递个函数

输入两个整数,计算他们的平方差并显示,代码如下:

#include//计算整数的平方int sqr(int a){return a * a;}//返回差值int diff(int a, int b){return (a > b " />- b : b - a);}int main(){int x = 0;int y = 0;puts("请输入两个整数。");printf("整数1:");scanf("%d", &x);printf("整数2:");scanf("%d", &y);printf("x和y的平方差是%d。\n", diff(sqr(x), spr(y)));return 0;}

运行结果:

函数sqr会返回形参a所接收值的平方,所以函数调用表达式sqr(x)和sqr(y)的判断结果是16和25,这两个数会被直接作为调用函数diff时的实参传递。因此函数表达式diff(sqr(x),sqr(y))就是diff(16,25),对该表达式进行判断,就会得到函数diff返回的9。

调用其他函数

在自己创建的函数中也可以调用其他函数,求4个数中的最大值代码如下:

#include//求两个数最大值int max2(int x, int y){return (x > y " />: y);}//求4个数的最大值int max4(int a, int b, int c, int d){return max2(max2(a, b), maxz(b, c));}int main(){int n1 = 0;int n2 = 0;int n3 = 0;int n4 = 0;puts("请输入4个整数。");printf("整数1:");scanf("%d", &n1);printf("整数2:");scanf("%d", &n2);printf("整数3:");scanf("%d", &n3);printf("整数4:");scanf("%d", &n4);printf("最大值是:%d\n", max4(n1, n2, n3, n4));return 0;}

我们可以认为函数就是程序的一个零件,例如,想要实现显示功能的时候,就调用printf这个零件。在制作零件的过程中,如果有其他方便的零件,我们也可以大量的使用。

值传递

下面是一个计算幂的函数,如果n是整数,则通过对x进行n次乘法运算得出的n次方幂,代码如下:

#includedouble power(double a, int b){int i = 0;double temp = 1.0;for ( i = 1; i <= b; i++){temp *= a;}return temp;}int main(){double a = 0.0;int b = 0;printf("求a的b次方幂。\n");printf("实数a:");scanf("%d", &a);printf("整数b:");scanf("%d", &b);printf("%.2f的%d次幂是%.2f", a, b, power(a, b));return 0;}

如上所示,形参a被赋上实参a的值,形参b被赋上实参b的值,像这样通过值来进行参数传递的机制称为值传递。
函数间参数的传递是通过值传递进行的。
这就相当于我们复印一本书,在复印版的书上用红色铅笔写写画画,不会对原来那本书造成任何影响。
形参a是实参a的副本,形参b是实参b的副本。所以在被调用一方的函数中,即使改变接收的形参的值,调用一方的实参也不会改变。

函数设计

上面讲到了函数定义和函数调用相关的基础知识,下面来讲更加正式的函数创建方法等。

没有返回值的函数

显示出一个直角在左下方的等腰 直角三角形,代码如下:

#includevoid put_stars(int n){while (n-->0){putchar('*');}}int main(){int i = 0;int len = 0;printf("生成一个直角在左下方的等腰直角三角形。\n");printf("短边;");scanf("%d", &len);for ( i = 0; i < len; i++){put_stars(i);putchar('\n');}}

本函数只是用来进行显示的,因此没有需要返回的值,这种没有返回值的类型,要声明为void。

通用性

通过函数使用put_stars可以把用于显示三角形的二重循环简化为一重循环,从而提高程序的可读性。
显示直角在右下方的等腰直角三角形代码如下:

#includevoid put_chars(int ch, int n){while (n-->0){putchar(ch);}}int main(){int i = 0;int len = 0;printf("生成一个直角在右下方的等腰直角三角形。\n");printf("短边:");scanf("%d", &len);for ( i = 0; i < len; i++){put_chars(' ', len - i);put_chars('*', i);putchar('\n');}return 0;}

本程序还需要连续显示空白字符,因此需要创建另一个函数put_chars来代替函数put_stars。该函数可以连续显示出n个通过形参传递过来的字符。

不含形参的函数

输入一个正整数并显示其倒转之后的值,代码如下:

#includeint scan_pint(void){int temp = 0;do{printf("请输入一个正整数;");scanf("%d", &temp);if (temp<=0){puts("\a请不要输入非正整数。");}} while (temp<=0);return temp;}int rev_int(int num){int temp = 0;if (num>0){do{temp = temp * 10 + num % 10;num /= 10;} while (num>0);}return temp;}int main(){int nx = scan_pint();printf("该整数倒转之后的值是%d。\n", rev_int(nx));return 0;}

函数scan_pint读取从键盘输入的正整数并返回,该函数不接收形参,为了加以说明在小括号里面写了void。

函数返回值的初始化

main函数中声明变量nx的部分,该变量的初始值是函数scan_pint()的调用表达式,变量nx使用函数的返回值进行初始化。

作用域

函数scan_pint和函数rev_pint都包含一个拥有相同标识符的变量,但是它们却是各自独自不同的变量。
也就是说,函数scan_pint中的temp变量是函数scan_pint特有的变量,而rev_pint中的变量temp是函数rev_pint中特有的变量。
赋给变量的标识符,它的名称都有个一通用的范围,称为作用域
在程序块中声明的变量的名称,只在该程序块中通用,在其他区域都无效,也就是说,变量的名称从变量声明的位置开始,到包含该声明的程序块最后的大括号为止,这一区间内通用,这样的作用域称为块作用域

文件作用域

输入5名学生的分数,显示其中的最高分,代码如下:

#include#define NU5int tensu[NU];int top(void);int mian(){externint tensu[];int i = 0;printf("请输入%d学生的分数。\n",NU);for ( i = 0; i < NU; i++){printf("%d:", i + 1);scanf("%d", &tensu[i]);}printf("最高分=%d\n", top());return 0;}int top(void){externint tensu[];int i = 0;int max = tensu[0];for ( i = 0; i < NU; i++){if (tensu[i]>max){max = tensu[i];}}return max;}

在函数的程序块中声明的变量等标识符是该程序块特有的部分,而像数组tensu这样,在函数外声明的变量标识符,其名称从声明的位置开始,到该程序的结尾都是通用的。这样的作用域称为文件作用域。

声明和定义

在上面的程序中声明,一个名为tensu的数组,像这样创建变量实体的声明称为定义声明,另外使用extern的声明表示”使用的在某处创建的tensu“,这里并没有真正创建出变量的实体,因此称为非定义声明。
由于数组tensu是在函数外定义的,所以只需要在main函数或top函数中明确声明要使用它,就可以放心使用。

函数原型声明

编译器在读取数据的时候,也是按照从头到尾的顺序依次读取的,因为本程序中函数top的函数定义在main函数之后,所以要想在main函数中调用top函数,编译器就需要知道。
因此就需要如下声明:
int top(void);
像这样明确记述了函数的返回类型,以及形参的类型和个数等的声明称为函数原型声明。
函数原型声明只声明了函数的返回值和形参等相关信息,并没有定义函数的实体。如果函数返回值的类型和形式参数发生了改变,那么函数定义和函数原型声明两部分都必须进行修改。

头文件和文件包含指令

通过函数原型声明,可以指定函数的参数以及返回值的类型等信息。这样就可以放心调用该函数了。
库函数printf或者putchar等的函数原型声明都包含在中,因此必须要使用下述固定的指令。
#include
通过#include指令,就可以把中的全部内容都读取到程序中。包含库函数的函数原型声明的称为头文件,而取得头文件内容的#include指令称为文件包含指令。

函数的通用性

函数top的工作过程如下:
找出int型数组tensu最前面NU个元素的最大值,然后返回该值。
当我们创建函数的时候就需要考虑函数的通用性。

可以处理任意数组

可以处理不同元素个数的数组

数组的传递

计算英语分数和数学分数,代码如下:

#include#define NU 5int max_of(int v[],int n){int i = 0;int max = v[0];for ( i = 1; i < n; i++){if (v[i]>max){max = v[i];}}return max;}int main(){int i = 0;int eng[NU];int mat[NU];int max_e = 0;int max_m = 0;printf("请输入%d名学生的分数。\n", NU);for ( i = 0; i < NU; i++){printf("[%d]英语:", i + 1);scanf("%d", &eng[NU]);printf("数学:");scanf("%d", &mat[NU]);}max_e = max_of(eng, NU);max_m = max_of(mat, NU);printf("英语最高分=%d\n", max_e);printf("数学最高分=%d\n", max_m);return 0;}

函数max_of的动作如下:
找出包含任意个元素的int 类型数组中的元素的最大值,然后返回该值。

函数的传递和const类型的修饰符

被调用函数中作为形参接收到的数组,就是函数调用时被作为实参的数组。
因此,对接受的数组元素进行修改,也会反映到调用时传入的数组中,下面让我们看以下代码:

#includevoid set_zero(int v[], int n){int i = 0;for ( i = 0; i < n; i++){v[i] = 0;}}void printf_array(const int v[], int n){int i = 0;printf("{");for ( i = 0; i < n; i++){printf("%d", v[i]);}printf("}");}int main(){int ary1[] = {1,2,3,4,5};int ary2[] = { 3,2,1 };printf("ary1=");printf_array(ary1, 5);putchar('\n');printf("ary2=");printf_array(ary2, 3);putchar('\n');set_zero(ary1, 5);set_zero(ary2, 3);printf("把0赋值给两个数组的所有元素。\n");printf("ary1=");printf_array(ary1, 5);putchar('\n');printf("ary2=");printf_array(ary2, 3);putchar('\n');return 0;}

为了解决这个问题,C语言提供了禁止在函数内修改接收到的数组内容的方法,只要在声明形参的时候加上被称为const的类型修饰符就可以了。
如果只是引用所接收的数组元素的值,而不改写的话,在声明接收数组的形参,就应该加上const。这样函数调用方就可以放心的调用函数了。

线性查找(顺序查找)

在数组中查找目标值的程序代码如下;

 #include#define NU 5#define FAILD -1int search(const int vx[], int key, int n){int i = 0;while (1){if (i==n){return FAILD;}if (vx[i]==key){return i;}i++;}}int main(){int i = 0;int ky = 0;int idx = 0;int vx[NU];for ( i = 0; i < NU; i++){printf("vx[%d]:", i);scanf("%d", &vx[i]);}printf("要查找的值:");scanf("%d", &ky);//从元素个数为NU的数组中查找kyidx = search(vx, ky, NU);if (idx==FAILD){puts("查找失败。");}else{printf("%d是数组的第%d号元素。\n", ky, idx + 1);}return 0;}

函数search从元素数为n的int型数组vx的开头,顺次查找是否存在与key值相同的元素,如果有,则返回数组元素下标。如果没有,则返回FAILD,也就是-1。
函数search中while语句的控制表达式是”1“,因此只有在执行return 语句的时候才跳出循环,否则循环体将会一直重复执行下去。
像这样,从数组的开头出发顺次搜索,找出与目标的元素的一系列操作,称为线性查找或者顺序查找

哨兵查找法

进行循环操作的时候,需要不停判断是否满足两个结束循环条件,虽说判断很简单,但是经过数次累积之后,也是一个不小的负担。
如果数组的大小还有富余,我们就可以把想要查找的数值存储到数组的末尾的元素v[n]中,这样一来,即使数组没有想要查找的数值,当遍历到v[n]的时候,也会满足条件。
在数组末尾追加数据称为哨兵,使用哨兵进行查找的方法称为哨兵查找法。使用这种方法可以简化对循环结束条件的判断。
代码如下:

#include#define NU 5#define FAILED -1int search(int v[], int key, int n){int i = 0;v[n] = key;while (1){if (v[i]==key){break;}i++;}return (i < n ? i : FAILED);}int main(){int i = 0;int ky = 0;int idx = 0;int vx[NU + 1];for ( i = 0; i < NU; i++){printf("vx[%d]:", i);scanf("%d", &vx[i]);}printf("要查找的值:");scanf("%d", &ky);if ((idx=search(vx,ky,NU))==FAILED){puts("\a查找失败。");}else{printf("%d是数组的第%d号元素。\n", ky, idx + 1);}return 0;}

由于函数search需要改变数组v的内容,因此在声明形参的时候不能加入const类型修饰符。
使用赋值运算符=进行赋值
将函数search的返回值赋给变量idx。
使用相等运算符==进行相等性的判断
判断赋值表达式idx=search(vx,ky,NU)和FAILED是否相等。

多维数组的传递

求4名学生在两次考试中3课程的的总分并显示。
代码如下:

#includevoid mat_add(const int a[4][3], const int b[4][3], int c[4][3]){int i = 0;int j = 0;for ( i = 0; i < 4; i++){for ( j = 0; j < 3; j++){c[i][j] = a[i][j] + b[i][j];}}}void mat_printf(const int m[4][3]){int i = 0;int j = 0;for ( i = 0; i < 4; i++){for ( j = 0; j < 3; j++){printf("%4d", m[i][j]);}putchar('\n');}}int main(){int tensu1[4][3] = { {91,63,78},{67,72,46},{89,34,53},{32,54,34} };int tensu2[4][3] = { {97,67,82},{73,43,46},{97,56,21},{85,46,35} };intsum[4][3];mat_add(tensu1, tensu2, sum);puts("第一次考试的分数:");mat_printf(tensu1);puts("第二次考试的分数:");mat_printf(tensu2);puts("总分:");mat_printf(sum);return 0;}

代码运行结果:

作用域和存储期

要创建大规模程序,必须首先理解作用域和存储期。

作用域和标识符的可见性

在下面的程序中对变量x的声明总共有三处,代码如下:

#includeint x = 75;void printf_x(void){printf("x=%d\n", x);}int main(){int i = 0;int x = 999;printf_x();printf("x=%d\n", x);for ( i = 0; i < 5; i++){int x = i * 100;printf("x=%d\n", x);}printf("x=%d\n", x);return 0;}

运行结果:

首先我们来看下int x = 75;处声明的x。该变量的初始值为75,因为它在函数外面声明定义的,所以这个x拥有文件作用域。
因此,函数print_x中的”x“就是上述的x,程序执行后,屏幕上会输出
x=75 ……显示的是x的值
因为printf_x();处调用了函数printf_x,所以会首先进行上面的打印显示。
注意:
如果两个同名变量分别拥有文件作用域和块作用域,那么只要拥有块作用域的变量是”可见“的,而拥有文件作用域的变量会被”隐藏“起来。
当同名变量都被赋予了块作用域的时候,内层的变量是”可见“的,而外层的变量会被”隐藏“起来。

存储期

在函数中声明的变量,并不是从程序开始到程序结束始终有效的,变量的生存期也就是寿命有两种,它们可以通过存储期这个概念来实现。
代码如下:

#includeint fx = 0;void func(void){static int sx = 0;int ax = 0;printf("%3d%3d%3d\n", ax++, sx++, fx++);}int main(){int i = 0;puts("ax sx fx");puts("----------");for ( i = 0; i < 10; i++){func();}puts("----------");return 0;}

运行结果:

在函数func中声明了sx和ax两个变量,但是声明sx的时候我们使用了存储类说明符static。可能正因为如此,虽然使用相同的值进行初始化并递增的,但最终的ax和sx的值并不相同。

  • 自动存储期

在函数中不使用存储类说明符static而定义出的对象(变量),被赋予了自动存储期,它具有以下特点:
程序执行到对象声明的时候就创建出了相对应的对象,而执行到包含该声明的程序块的结尾,也就是大括号的时候,该对象就会消失。

  • 静态存储期

在函数中使用static定义出来的对象,或者在函数外声明定义出来的对象都被赋予了静态存储期,它具有以下特点:
在程序开始执行的时候,就具体地说是在main函数执行之前的准备阶段被创建出来,在程序结束的时候消失。
也就是说,该对象拥有了”永久“的寿命。另外如果不显示地进行初始化,则该对象会自动初始化为0。
对象的存储期如下图:

总结

  • 将多个处理集中到一起进行时,可以使用函数这一程序的零件。返回类型、函数名、形参这三个部分决定了函数的特征。不接收参数的函数,其形参类型为void。
  • 函数体是复合语句(程序块)。如果有仅在函数中使用的变量,原则上应在该函数中声明和使用。
  • 函数调用的形式是在函数名后面加上小括号,这个括号称为函数调用运算符。如果没有实参,则小括号为空。有多个实参的情况下,使用逗号隔开。
  • 进行函数调用后,程序的流程将一下子跳转到该函数处。




以上就是我关于函数的介绍,希望都帮到大家。也请大家给我指点不足,谢谢!!!