——函数)
Posted 二木成林
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了——函数)相关的知识,希望对你有一定的参考价值。
8.1 概述
函数实现特定功能。由于采用了函数模块式的结构,C语言易于实现结构化程序设计。使程序的层次结构清晰,便于程序的编写、阅读、调试。在C语言中可从不同的角度对函数分类。
从函数定义的角度来看,函数可分为库函数和用户自定义函数:
- 库函数:由C系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。如前面提到的printf、scanf、getchar、putchar等。
- 用户定义函数:由用户按需要写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。
从函数的返回值情况来看,又可以分为有返回值函数和无返回值函数:
- 有返回值函数:此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。由用户定义的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型。
- 无返回值函数:此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。由于函数无须返回值,用户在定义此类函数时可指定它的返回为“空类型”,空类型的说明符为“void”。
从函数的参数角度来看,又可以分为有参函数和无参函数:
- 无参函数:函数定义、函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。
- 有参函数:也称为带参函数。在函数定义及函数说明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)。进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。
C语言提供了极为丰富的库函数,这些库函数又可从功能角度作以下分类:
- 字符类型分类函数:用于对字符按ASCII码分类:字母,数字,控制字符,分隔符,大小写字母等。
- 转换函数:用于字符或字符串的转换;在字符量和各类数字量(整型,实型等)之间进行转换;在大、小写之间进行转换。
- 目录路径函数:用于文件目录和路径操作。
- 诊断函数:用于内部错误检测。
- 图形函数:用于屏幕管理和各种图形功能。
- 输入输出函数:用于完成输入输出功能。
- 接口函数:用于与DOS,Bios和硬件的接口。
- 字符串函数:用于字符串操作和处理。
- 内存管理函数:用于内存管理。
- 数学函数:用于数学函数计算。
- 日期和时间函数:用于日期,时间转换操作。
- 进程控制函数:用于进程管理和控制。
- 其它函数:用于其它各种功能。
注意:
- 在C语言中,不允许在一个函数的函数体内定义另外一个函数。
- 函数之间允许相互调用,也允许嵌套调用,还可以自己调用自己称为递归调用。
- main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。
- C程序的执行总是从main函数开始,完成对其它函数的调用后再返回到main函数,最后由main函数结束整个程序。一个C源程序必须有,也只能有一个主函数main。
8.2 函数定义的一般形式
8.2.1 无参函数的定义形式
无参函数定义的语法如下:
类型说明符 函数名()
声明部分;
语句;
说明:
- 其中类型标识符和函数名称为函数头。类型标识符指明了本函数的类型,函数的类型实际上是函数返回值的类型,如
int
等。 - 函数名是由用户定义的标识符,函数名后有一个空括号,其中无参数,但括号不可少。
中的内容称为函数体。在函数体中声明部分,是对函数体内部所用到的变量的类型说明,如
int a,b,c;
。- 在很多情况下都不要求无参函数有返回值,此时函数类型符可以写为
void
。
实例:
/**
* 定义一个hello函数
*/
void hello()
printf("hello world!");
8.2.2 有参函数定义的一般形式
语法:
类型说明符 函数名(形式参数列表)
声明部分;
语句;
说明:
- 在形参表中给出的参数称为形式参数,它们可以是各种类型的变量,各参数之间用逗号间隔。
- 在进行函数调用时,主调函数将赋予这些形式参数实际的值。形参既然是变量,必须在形参表中给出形参的类型说明。
- 有返回值函数中至少应有一个return语句。
- 在C程序中,一个函数的定义可以放在任意位置,既可放在主函数main之前,也可放在main之后。
示例:
#include <stdio.h>
/**
* 求两个数中的大数
* @param a 第一个数
* @param b 第二个数
* @return 两个数中较大的数
*/
int max(int a, int b)
return a > b ? a : b;
int main()
// 在main函数中调用定义的函数
// a和b是形参;3和5是实参
int result = max(3, 5);
printf("result=%d", result);
8.3 函数的参数和函数的值
8.3.1 形式参数和实际参数
函数的参数分为形参和实参两种:
- 形参:出现在函数定义中,在函数体内使用,离开函数体不能使用。
- 实参:出现在主调函数中,只有在调用函数时才需要传递实参。
形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。函数的形参和实参具有以下特点:
- 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
- 参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值。
- 实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配的错误。
- 函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
示例:
#include <stdio.h>
void loop(int n)
for (int i = n; i >= 1; i--)
n += i;
printf("loop: n=%d", n);// 20
int main()
// 可见实参的值不随形参的变化而变化
int n = 5;
loop(n);
printf("main: n=%d", n);// 5
8.3.2 函数的返回值
函数的值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。函数的返回值说明如下:
- 函数的值只能通过
return
语句返回主调函数。基本语法如下:return 表达式;
。 - 在函数中允许有多个return语句,但每次调用只能有一个return 语句被执行,因此只能返回一个函数值。如
if...else...
语句中可以有多个return语句,但最终只有一个被执行。 - 函数值的类型和函数定义中函数的类型应保持一致。如果两者不一致,则以函数类型为准,自动进行类型转换。
- 如函数返回值值为整型,则在函数定义时可以省去类型说明。如
int hello()
可以省略为hello()
。 - 不返回函数值的函数,可以明确定义为“空类型”,类型说明符为“void”。如
void hello()
。 - 为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为空类型
void
。
8.4 函数的调用
8.4.1 函数调用的一般形式
在程序中是通过对函数的调用来执行函数体的。C语言中函数调用的语法如下:
函数名(实际参数列表);
对无参函数调用时则无实际参数表。实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式。各实参之间用逗号分隔。如hello("hello world");
、add(1, 2);
。
8.4.2 函数调用的方式
在C语言中,调用函数的几种方式如下:
- 函数表达式:函数作为表达式中的一项出现在表达式中,以函数返回值参与表达式的运算,这种方式要求函数是有返回值的。如
int z=max(3, 4);
。 - 函数语句:函数调用的一般形式加上分号即构成函数语句。如
printf("%d", a);
等。 - 函数实参:函数作为另一个函数调用的实际参数出现,这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。如
printf("%d", max(a, b));
。
8.4.3 被调用函数的声明和函数原型
被调用函数的声明目的:
- 在主调函数中调用某函数之前应对该被调函数进行说明(声明),这与使用变量之前要先进行变量说明是一样的。
- 在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理。
被调用函数的声明语法:
类型说明符 被调用函数名(类型 形参, 类型 形参...);// 括号内给出了形参的类型和形参名,或只给出形参类型。这便于编译系统进行检错,以防止可能出现的错误。
// 或
类型说明符 被调用函数名(类型, 类型...);
示例:
int max(int a, int b);
// 或
int max(int, int);
被调用函数的声明注意事项:
- 如果被调函数的返回值是整型或字符型时,可以不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。
#include <stdio.h>
int main()
float m = max(3, 4);
printf("%d", m);
// 如果被调函数的返回值是整型或字符型时,可以不对被调函数作说明,而直接调用。
int max(int a, int b)
return a > b ? a : b;
- 当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作说明而直接调用。
#include <stdio.h>
// 当被调函数的函数定义出现在主调函数(main)之前时,在主调函数中也可以不对被调函数再作说明而直接调用。
int max(int a, int b)
return a > b ? a : b;
int main()
float m = max(3, 4);
printf("%d", m);
- 如果在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。
#include <stdio.h>
// 如果在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。
int max(int a, int b);
int main()
float m = max(3, 4);
printf("%d", m);
int max(int a, int b)
return a > b ? a : b;
- 对库函数的调用不需要再作说明,但必须把该函数的头文件用include命令包含在源文件前部。
#include <stdio.h>
// 对库函数的调用不需要再作说明,但必须把该函数的头文件用include命令包含在源文件前部。
#include <math.h>
int main()
printf("%f", sqrt(16));
8.5 函数的嵌套调用
C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用,即在被调函数中又调用其它函数。
#include <stdio.h>
// 声明函数
long pow(int x, int y);
long add(int a, int b);
long factorial(int num);
int main()
// 计算s = (2^2)! + (3^2)!
long s;
s = add(factorial(pow(2, 2)), factorial(pow(3, 2)));
printf("(2^2)! + (3^2)! = %d", s);
/**
* 计算x的y次方
* @param x 待计算的数
* @param y 次方
* @return x的y次方的结果
*/
long pow(int x, int y)
long result = 1;
for (int i = 1; i <= y; i++)
result *= x;
return result;
/**
* 计算两数之和
* @param a 一个数
* @param b 另外一个数
* @return 两数之和
*/
long add(int a, int b)
return a + b;
/**
* 计算指定数的阶乘
* @param num 指定数
* @return 阶乘结果
*/
long factorial(int num)
long result = 1;
for (int i = num; i >= 1; i--)
result *= i;
return result;
8.6 函数的递归调用
函数自己调用自己称之为递归调用。C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。如:
#include <stdio.h>
void hello(msg)
printf(msg);
// 这就是一个递归函数,在函数中调用了自身
hello("hello world\\n");
int main()
hello("abc");
注意,为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。例如,利用递归来计算阶乘:
#include <stdio.h>
/**
* 利用递归来计算阶乘
* 用递归法计算n!可用下述公式表示:
* n!=1 (n=0,1)
* n×(n-1)! (n>1)
* @param num
* @return
*/
long factorial(int num)
if (num == 1 || num == 0)
return 1;
return num * factorial(num - 1);
int main()
long r = factorial(5);
printf("%ld", r);
8.7 数组作为函数参数
数组可以作为函数的参数使用,进行数据传送。数组用作函数参数有两种形式:
- 一种是把数组元素(下标变量)作为实参使用。
- 另一种是把数组名作为函数的形参和实参使用。
8.7.1 数组元素作函数实参
数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。
#include <stdio.h>
void add(int a, int b)
int result = a + b;
printf("%d + %d = %d", a, b, result);
int main()
int nums[] = 4, 5;
// 将数组中的元素作为实参传递给函数
add(nums[0], nums[1]);
8.7.2 数组名作为函数参数
用数组名作函数参数与用数组元素作实参有几点不同:
- 用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时,则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。
- 在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。我们知道数组名就是数组的首地址,因此在数组名作函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。
- 在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同,而形参的值发生改变后,实参并不变化,两者的终值是不同的。而当用数组名作函数参数时,情况则不同。由于实际上形参和实参为同一数组,因此当形参数组发生变化时,实参数组也随之变化。当然这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后实参数组的值将由于形参数组值的变化而变化。
示例1:将数组名作为函数实参传递
#include <stdio.h>
// 传递一个数组作为函数形参
int sum(int nums[2])
return nums[0] + nums[1];
int main()
int nums[] = 4, 5;
int r = sum(nums);// 将函数名作为实参传递给函数
printf("%d", r);
示例2:修改形参数组也会影响实参数组
#include <stdio.h>
void print(int nums[5])
printf("\\n");
for (int i = 0; i < 5; i++)
nums[i] = nums[i] * nums[i];
printf("%d ", nums[i]);
printf("\\n");
int main()
// 调用函数后,修改形参数组的值也会影响到实参数组的值,因为它们都是同一个数组
int nums[] = 1, 2, 3, 4, 5;
// 打印数组
for (int i = 0; i < 5; i++)
printf("%d ", nums[i]);
// 调用函数,修改函数中元素的值
print(nums);
// 再次打印数组
for (int i = 0; i < 5; i++)
printf("%d ", nums[i]);
/*打印结果:
1 2 3 4 5
1 4 9 16 25
1 4 9 16 25
*/
用数组名作为函数参数的注意事项:
- 形参数组和实参数组的类型必须一致,否则将引起错误。
- 形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。
#include <stdio.h>
void print(int nums[9])
printf("\\n");
// 注意,形参数组长度为9,但传递的实参数组长度为5,这里是不会报错的,即使数组下标超过了范围
// 在C语言中编译也能正常通过,但在其他高级语言如Java中数组下标超过范围则会报索引越界异常
for (int i = 0; i < 9; i++)
nums[i] = nums[i] * nums[i];
printf("%d ", nums[i]);
printf("\\n");
int main()
int nums[] = 1, 2, 3, 4, 5;
// 调用函数,修改函数中元素的值
print(nums);
- 在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。如
void print(int nums[9])
可以修改写成void print(int nums[])
或者void print(int nums[], int length)
,其中形参数组nums
没有给出长度,而由length
值动态地表示数组的长度。length
的值由主调函数的实参进行传送。
#include <stdio.h>
/**
* 计算数组中所有元素的总和
* @param nums 整型数组
* @param length 数组长度
* @return 数组中所有元素的总和
*/
int sum(int nums[], int length)
int result = 0;
for (int i = 0; i < length; i++)
result += nums[i];
return result;
int main()
int nums[] = 1, 2, 3, 4, 5;
int r = sum(nums, 5);
printf("%d", r);// 15
- 多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。如
int print(int nums[3][5])
或int print(int nums[][5])
都是合法的。
8.8 局部变量和全局变量
- 形参变量只在被调用期间才分配内存单元,调用结束立即释放,形参变量只有在函数内才是有效的,离开该函数就不能再使用了,这种变量有效性的范围称变量的作用域。
- 不仅对于形参变量,C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。
- C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。
8.8.1 局部变量
局部变量就是在函数内定义声明的变量,其作用域仅限于函数内,在函数外再使用这种变量是非法。
#include <stdio.h>
void fun(int a)
// 变量a、b、c仅在函数作用域内有效
int b = 5, c = 10;
int main()
fun(0);
printf("%d %d %d", a, b, c);// 这三个变量都无法使用
关于局部变量的注意事项:
- 主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。
- 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。
- 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。
- 在复合语句(即在
中定义的变量作用域范围仅限于
内)中也可定义变量,其作用域只在复合语句范围内。
#include <stdio.h>
int main()
int i = 2, j = 3, k;
k = i + j;
// 在复合语句中也可定义变量,其作用域只在复合语句范围内。
int k = 8;
printf("%d\\n", k);// 8
printf("%d\\n", k);// 5
8.8.2 全局变量
全局变量也称为外部变量,它是在函数外部定义的变量。
int a,b;// 全局变量,在函数外部定义的变量
void f1()// 函数f1
.函数指针和指针函数