作者:解学武
全方位讲解C语言函数(超级肝)
这篇文章带领读者全方位地学习 C语言函数的用法,想要彻底搞清楚 C语言中的函数,一定要看完这篇文章。
所谓函数,就是一段封装好的、可以重复使用的代码,它使得我们的程序更加模块化,不需要编写大量重复的代码。
函数可以提前保存起来,并给它起一个独一无二的名字,只要知道它的名字就能使用这段代码。函数还可以接收数据,并根据数据的不同做出不同的操作,最后再把处理结果反馈给我们。
不妨设想一下,如果没有 strcmp() 函数,要想比较两个字符串的大小该怎么写呢?请看下面的代码:
函数的本质是一段可以重复使用的代码,这段代码被提前编写好了,放到了指定的文件中,使用时直接调取即可。 下面我们就来演示一下如何封装 strcmp() 这个函数。
这是我们自己编写的函数,放在了当前源文件中(函数封装和函数使用在同一个源文件中),所以不需要引入头文件;而 C语言自带的 strcmp() 放在了其它的源文件中(函数封装和函数使用不在同一个源文件中),并在 string.h 头文件中告诉我们如何使用,所以我们必须引入 string.h 头文件。
我们自己编写的 strcmp_alias() 和原有的 strcmp() 在功能和格式上都是一样的,只是存放的位置不同,所以一个需要引入头文件,一个不需要引入。
本章我们重点讲解的内容就是如何将一段代码封装成函数,以及封装以后如何使用。
你看它们是何其相似,都是通过一定的操作或规则,由一份数据得到另一份数据。
不过从本质上看,将 Function 理解为“功能”或许更恰当,C语言中的函数往往是独立地实现了某项功能。一个程序由多个函数组成,可以理解为「一个程序由多个小的功能叠加而成」。
本教程重在实践,不咬文嚼字,不死扣概念,大家理解即可,不必在此深究。
C语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,可以简单地认为它是一系列函数的集合,在磁盘上往往是一个文件夹。C语言自带的库称为标准库(Standard Library),其他公司或个人开发的库称为第三方库(Third-Party Library)。
函数返回值有固定的数据类型(int、char、float 等),用来接收返回值的变量类型要一致。
将代码段封装成函数的过程叫做函数定义。
例如,定义一个函数,计算从 1 加到 100 的结果:
将上面的代码补充完整:
The sum is 5050
函数不能嵌套定义,main 也是一个函数定义,所以要将 sum 放在 main 外面。函数必须先定义后使用,所以 sum 要放在 main 前面。
数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。
更改上面的例子,计算从 m 加到 n 的结果:
调用 sum() 函数时,需要给它传递两份数据,一份传递给 m,一份传递给 n。你可以直接传递整数,例如:
原则上讲,实参的类型和数目要与形参保持一致。如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。
将上面的代码补充完整:
The sum from 5 to 86 is 3731
定义 sum() 时,参数 m、n 的值都是未知的;调用 sum() 时,将 begin、end 的值分别传递给 m、n,这和给变量赋值的过程是一样的,它等价于:
下面的例子是错误的:
正确的写法应该是这样的:
return 语句的一般形式为:
2) return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值(少数的编程语言支持多个返回值,例如Go语言)。例如:
3) 函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。例如:
下面我们定义了一个判断素数的函数,这个例子更加实用:
return 语句是提前结束函数的唯一办法。return 后面可以跟一份数据,表示将这份数据返回到函数外面;return 后面也可以不跟任何数据,表示什么也不返回,仅仅用来结束函数。
更改上面的代码,使得 return 后面不跟任何数据:
在C语言中,函数调用的方式有多种,例如:
【示例】计算sum = 1! + 2! + 3! + ... + (n-1)! + n!
分析:可以编写两个函数,一个用来计算阶乘,一个用来计算累加的和。
1!+2!+...+9!+10! = 4037913
sum() 的定义中出现了对 factorial() 的调用,printf() 的调用过程中出现了对 sum() 的调用,而 printf() 又被 main() 调用,它们整体调用关系为:
当主调函数遇到被调函数时,主调函数会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回主调函数,主调函数根据刚才的状态继续往下执行。
一个C语言程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条。这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如
函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码,当遇到函数调用时,CPU 首先要记录下当前代码块中下一条代码的地址(假设地址为 0X1000),然后跳转到另外一个代码块,执行完毕后再回来继续执行 0X1000 处的代码。整个过程相当于 CPU 开了一个小差,暂时放下手中的工作去做点别的事情,做完了再继续刚才的工作。
从上面的分析可以推断出,在所有函数之外进行加减乘除运算、使用 if...else 语句、调用一个函数等都是没有意义的,这些代码位于整个函数调用链条之外,永远都不会被执行到。C语言也禁止出现这种情况,会报语法错误,请看下面的代码:
在所有的函数中,main() 是入口函数,有且只能有一个,C语言程序就是从这里开始运行的。
C语言中所有的函数定义,包括主函数 main() 在内,都是平行的。也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数,被调用者称为被调函数。函数还可以自己调用自己,称为递归调用。
main() 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。因此,C程序的执行总是从 main() 函数开始,完成对其它函数的调用后再返回到 main() 函数,最后由 main() 函数结束整个程序。
声明:当前文章为本站“玩转C语言和数据结构”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
所谓函数,就是一段封装好的、可以重复使用的代码,它使得我们的程序更加模块化,不需要编写大量重复的代码。
函数可以提前保存起来,并给它起一个独一无二的名字,只要知道它的名字就能使用这段代码。函数还可以接收数据,并根据数据的不同做出不同的操作,最后再把处理结果反馈给我们。
C语言函数的概念
从表面上看,函数在使用时必须带上括号,有必要的话还要传递参数,函数的执行结果也可以赋值给其它变量。看一个实例,strcmp() 是一个用来比较字符串大小的函数,它的用法如下:#include <stdio.h> #include <string.h> int main(){ char str1[] = "https://xiexuewu.github.io"; char str2[] = "https://www.baidu.com"; //比较两个字符串大小 int result = strcmp(str1, str2); printf("str1 - str2 = %d\n", result); return 0; }str1 和 str2 是传递给 strcmp() 的参数,strcmp() 的处理结果赋值给了变量 result。
不妨设想一下,如果没有 strcmp() 函数,要想比较两个字符串的大小该怎么写呢?请看下面的代码:
#include <stdio.h> #include <string.h> int main(){ char str1[] = "https://xiexuewu.github.io"; char str2[] = "https://www.baidu.com"; int result, i; //比较两个字符串大小 for(i=0; (result = str1[i] - str2[i]) == 0; i++){ if(str1[i] == '\0' || str2[i] == '\0'){ break; } } printf("str1 - str2 = %d\n", result); return 0; }比较字符串大小是常用的功能,一个程序可能会用到很多次,如果每次都写这样一段重复的代码,不但费时费力、容易出错,而且交给别人时也很麻烦,所以 C语言提供了一个功能,允许我们将常用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就可以重复使用它,这个模块就叫做函数(Function)。
函数的本质是一段可以重复使用的代码,这段代码被提前编写好了,放到了指定的文件中,使用时直接调取即可。 下面我们就来演示一下如何封装 strcmp() 这个函数。
#include <stdio.h> //将比较字符串大小的代码封装成函数,并命名为strcmp_alias int strcmp_alias(char *s1, char *s2){ int i, result; for(i=0; (result = s1[i] - s2[i]) == 0; i++){ if(s1[i] == '\0' || s2[i] == '\0'){ break; } } return result; } int main(){ char str1[] = "https://xiexuewu.github.io"; char str2[] = "https://www.baidu.com"; char str3[] = "https://www.zhihu.com/people/data_structure"; //重复使用strcmp_alias()函数 int result_1_2 = strcmp_alias(str1, str2); int result_1_3 = strcmp_alias(str1, str3); printf("str1 - str2 = %d\n", result_1_2); printf("str1 - str3 = %d\n", result_1_3); return 0; }为了避免与原有的 strcmp 产生命名冲突,我将新函数命名为 strcmp_alias。
这是我们自己编写的函数,放在了当前源文件中(函数封装和函数使用在同一个源文件中),所以不需要引入头文件;而 C语言自带的 strcmp() 放在了其它的源文件中(函数封装和函数使用不在同一个源文件中),并在 string.h 头文件中告诉我们如何使用,所以我们必须引入 string.h 头文件。
我们自己编写的 strcmp_alias() 和原有的 strcmp() 在功能和格式上都是一样的,只是存放的位置不同,所以一个需要引入头文件,一个不需要引入。
本章我们重点讲解的内容就是如何将一段代码封装成函数,以及封装以后如何使用。
1) C语言中的函数和数学中的函数
美国人将函数称为“Function”。Function 除了有“函数”的意思,还有“功能”的意思,中国人将 Function 译为“函数”而不是“功能”,是因为C语言中的函数和数学中的函数在使用形式上有些类似,例如:- C语言中有 length = strlen(str)
- 数学中有 y = f(x)
你看它们是何其相似,都是通过一定的操作或规则,由一份数据得到另一份数据。
不过从本质上看,将 Function 理解为“功能”或许更恰当,C语言中的函数往往是独立地实现了某项功能。一个程序由多个函数组成,可以理解为「一个程序由多个小的功能叠加而成」。
本教程重在实践,不咬文嚼字,不死扣概念,大家理解即可,不必在此深究。
2) 库函数和自定义函数
C语言在发布时已经为我们封装好了很多函数,它们被分门别类地放到了不同的头文件中(暂时先这样认为),使用函数时引入对应的头文件即可。这些函数都是专家编写的,执行效率极高,并且考虑到了各种边界情况,各位读者请放心使用。C语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,可以简单地认为它是一系列函数的集合,在磁盘上往往是一个文件夹。C语言自带的库称为标准库(Standard Library),其他公司或个人开发的库称为第三方库(Third-Party Library)。
关于库的概念,我们已在《不要这样学习C语言,这是一个坑!》中进行了详细介绍。除了库函数,我们还可以编写自己的函数,拓展程序的功能。自己编写的函数称为自定义函数。自定义函数和库函数在编写和使用方式上完全相同,只是由不同的机构来编写。
3) 参数
函数的一个明显特征就是使用时带括号( )
,有必要的话,括号中还要包含数据或变量,称为参数(Parameter)。参数是函数需要处理的数据,例如:
-
strlen(str1)
用来计算字符串的长度,str1
就是参数。 -
puts("C语言")
用来输出字符串,"C语言"
就是参数。
4) 返回值
既然函数可以处理数据,那就有必要将处理结果告诉我们,所以很多函数都有返回值(Return Value)。所谓返回值,就是函数的执行结果。例如:char str1[] = "C Language"; int len = strlen(str1);strlen() 的处理结果是字符串 str1 的长度,是一个整数,我们通过 len 变量来接收。
函数返回值有固定的数据类型(int、char、float 等),用来接收返回值的变量类型要一致。
C语言函数的定义
函数是一段可以重复使用的代码,用来独立地完成某个功能,它可以接收用户传递的数据,也可以不接收。接收用户数据的函数在定义时要指明参数,不接收用户数据的不需要指明,根据这一点可以将函数分为有参函数和无参函数。将代码段封装成函数的过程叫做函数定义。
1) C语言无参函数的定义
如果函数不接收用户传递的数据,那么定义时可以不带参数。如下所示:
dataType functionName(){
//body
}
- dataType 是返回值类型,它可以是C语言中的任意数据类型,例如 int、float、char 等。
-
functionName 是函数名,它是标识符的一种,命名规则和标识符相同。函数名后面的括号
( )
不能少。 -
body 是函数体,它是函数需要执行的代码,是函数的主体部分。即使只有一个语句,函数体也要由
{ }
包围。 - 如果有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和 dataType 一样。
例如,定义一个函数,计算从 1 加到 100 的结果:
int sum(){ int i, sum=0; for(i=1; i<=100; i++){ sum+=i; } return sum; }累加结果保存在变量
sum
中,最后通过return
语句返回。sum 是 int 型,返回值也是 int 类型,它们一一对应。return
是C语言中的一个关键字,只能用在函数中,用来返回处理结果。将上面的代码补充完整:
#include <stdio.h> int sum(){ int i, sum=0; for(i=1; i<=100; i++){ sum+=i; } return sum; } int main(){ int a = sum(); printf("The sum is %d\n", a); return 0; }运行结果:
The sum is 5050
函数不能嵌套定义,main 也是一个函数定义,所以要将 sum 放在 main 外面。函数必须先定义后使用,所以 sum 要放在 main 前面。
注意:main 是函数定义,不是函数调用。当可执行文件加载到内存后,系统从 main 函数开始执行,也就是说,系统会调用我们定义的 main 函数。
无返回值函数
有的函数不需要返回值,或者返回值类型不确定(很少见),那么可以用 void 表示,例如:void hello(){ printf ("Hello,world \n"); //没有返回值就不需要 return 语句 }
void
是C语言中的一个关键字,表示“空类型”或“无类型”,绝大部分情况下也就意味着没有 return 语句。
2) C语言有参函数的定义
如果函数需要接收用户传递的数据,那么定义时就要带上参数。如下所示:
dataType functionName( dataType1 param1, dataType2 param2 ... ){
//body
}
dataType1 param1, dataType2 param2 ...
是参数列表。函数可以只有一个参数,也可以有多个,多个参数之间由,
分隔。参数本质上也是变量,定义时要指明类型和名称。与无参函数的定义相比,有参函数的定义仅仅是多了一个参数列表。数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。
更改上面的例子,计算从 m 加到 n 的结果:
int sum(int m, int n){ int i, sum=0; for(i=m; i<=n; i++){ sum+=i; } return sum; }参数列表中给出的参数可以在函数体中使用,使用方式和普通变量一样。
调用 sum() 函数时,需要给它传递两份数据,一份传递给 m,一份传递给 n。你可以直接传递整数,例如:
int result = sum(1, 100); //1传递给m,100传递给n
也可以传递变量:
int begin = 4;
int end = 86;
int result = sum(begin, end); //begin传递给m,end传递给n
int num = 33;
int result = sum(num, 80); //num传递给m,80传递给n
原则上讲,实参的类型和数目要与形参保持一致。如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。
将上面的代码补充完整:
#include <stdio.h> int sum(int m, int n){ int i, sum=0; for(i=m; i<=n; i++){ sum+=i; } return sum; } int main(){ int begin = 5, end = 86; int result = sum(begin, end); printf("The sum from %d to %d is %d\n", begin, end, result); return 0; }运行结果:
The sum from 5 to 86 is 3731
定义 sum() 时,参数 m、n 的值都是未知的;调用 sum() 时,将 begin、end 的值分别传递给 m、n,这和给变量赋值的过程是一样的,它等价于:
m = begin; n = end;
函数不能嵌套定义
强调一点,C语言不允许函数嵌套定义;也就是说,不能在一个函数中定义另外一个函数,必须在所有函数之外定义另外一个函数。main() 也是一个函数定义,也不能在 main() 函数内部定义新函数。下面的例子是错误的:
#include <stdio.h> void func1(){ printf("https://xiexuewu.github.io"); void func2(){ printf("C语言入门教程"); } } int main(){ func1(); return 0; }有些初学者认为,在 func1() 内部定义 func2(),那么调用 func1() 时也就调用了 func2(),这是错误的。
正确的写法应该是这样的:
#include <stdio.h> void func2(){ printf("C语言入门教程"); } void func1(){ printf("https://xiexuewu.github.io"); func2(); } int main(){ func1(); return 0; }func1()、func2()、main() 三个函数是平行的,谁也不能位于谁的内部,要想达到「调用 func1() 时也调用 func2()」的目的,必须将 func2() 定义在 func1() 外面,并在 func1() 内部调用 func2()。
有些编程语言是允许函数嵌套定义的,例如 JavaScript,在 JavaScript 中经常会使用函数的嵌套定义。C语言函数返回值 函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。
return 语句的一般形式为:
return 表达式;或者:
return (表达式);有没有
( )
都是正确的,为了简明,一般也不写( )
。例如:
return max; return a+b; return (100+200);
对C语言返回值的说明:
1) 没有返回值的函数为空类型,用void
表示。例如:
void func(){ printf("https://xiexuewu.github.io\n"); }一旦函数的返回值类型被定义为 void,就不能再接收它的值了。例如,下面的语句是错误的:
int a = func();为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为 void 类型。
2) return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值(少数的编程语言支持多个返回值,例如Go语言)。例如:
//返回两个整数中较大的一个 int max(int a, int b){ if(a > b){ return a; }else{ return b; } }如果
a>b
成立,就执行return a
,return b
不会执行;如果不成立,就执行return b
,return a
不会执行。3) 函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。例如:
//返回两个整数中较大的一个 int max(int a, int b){ return (a>b) ? a : b; printf("Function is performed\n"); }第 4 行代码就是多余的,永远没有执行的机会。
下面我们定义了一个判断素数的函数,这个例子更加实用:
#include <stdio.h> int prime(int n){ int is_prime = 1, i; //n一旦小于0就不符合条件,就没必要执行后面的代码了,所以提前结束函数 if(n < 0){ return -1; } for(i=2; i<n; i++){ if(n % i == 0){ is_prime = 0; break; } } return is_prime; } int main(){ int num, is_prime; scanf("%d", &num); is_prime = prime(num); if(is_prime < 0){ printf("%d is a illegal number.\n", num); }else if(is_prime > 0){ printf("%d is a prime number.\n", num); }else{ printf("%d is not a prime number.\n", num); } return 0; }prime() 是一个用来求素数的函数。素数是自然数,它的值大于等于零,一旦传递给 prime() 的值小于零就没有意义了,就无法判断是否是素数了,所以一旦检测到参数 n 的值小于 0,就使用 return 语句提前结束函数。
return 语句是提前结束函数的唯一办法。return 后面可以跟一份数据,表示将这份数据返回到函数外面;return 后面也可以不跟任何数据,表示什么也不返回,仅仅用来结束函数。
更改上面的代码,使得 return 后面不跟任何数据:
#include <stdio.h> void prime(int n){ int is_prime = 1, i; if(n < 0){ printf("%d is a illegal number.\n", n); return; //return后面不带任何数据 } for(i=2; i<n; i++){ if(n % i == 0){ is_prime = 0; break; } } if(is_prime > 0){ printf("%d is a prime number.\n", n); }else{ printf("%d is not a prime number.\n", n); } } int main(){ int num; scanf("%d", &num); prime(num); return 0; }prime() 的返回值是 void,return 后面不能带任何数据,直接写分号即可。 C语言函数的调用 所谓函数调用(Function Call),就是使用已经定义好的函数。函数调用的一般形式为:
functionName(param1, param2, param3 ...);
functionName 是函数名称,param1, param2, param3 ...
是实参列表。实参可以是常数、变量、表达式等,多个实参用逗号,
分隔。在C语言中,函数调用的方式有多种,例如:
//函数作为表达式中的一项出现在表达式中 z = max(x, y); m = n + max(x, y); //函数作为一个单独的语句 printf("%d", a); scanf("%d", &b); //函数作为调用另一个函数时的实参 printf( "%d", max(x, y) ); total( max(x, y), min(m, n) );
函数的嵌套调用
函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。【示例】计算sum = 1! + 2! + 3! + ... + (n-1)! + n!
分析:可以编写两个函数,一个用来计算阶乘,一个用来计算累加的和。
#include <stdio.h> //求阶乘 long factorial(int n){ int i; long result=1; for(i=1; i<=n; i++){ result *= i; } return result; } // 求累加的和 long sum(long n){ int i; long result = 0; for(i=1; i<=n; i++){ //在定义过程中出现嵌套调用 result += factorial(i); } return result; } int main(){ printf("1!+2!+...+9!+10! = %ld\n", sum(10)); //在调用过程中出现嵌套调用 return 0; }运行结果:
1!+2!+...+9!+10! = 4037913
sum() 的定义中出现了对 factorial() 的调用,printf() 的调用过程中出现了对 sum() 的调用,而 printf() 又被 main() 调用,它们整体调用关系为:
main() --> printf() --> sum() --> factorial()
如果一个函数 A() 在定义或调用过程中出现了对另外一个函数 B() 的调用,那么我们就称 A() 为主调函数或主函数,称 B() 为被调函数。当主调函数遇到被调函数时,主调函数会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回主调函数,主调函数根据刚才的状态继续往下执行。
一个C语言程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条。这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如
return 0;
)来结束自己的生命,从而结束整个程序。函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码,当遇到函数调用时,CPU 首先要记录下当前代码块中下一条代码的地址(假设地址为 0X1000),然后跳转到另外一个代码块,执行完毕后再回来继续执行 0X1000 处的代码。整个过程相当于 CPU 开了一个小差,暂时放下手中的工作去做点别的事情,做完了再继续刚才的工作。
从上面的分析可以推断出,在所有函数之外进行加减乘除运算、使用 if...else 语句、调用一个函数等都是没有意义的,这些代码位于整个函数调用链条之外,永远都不会被执行到。C语言也禁止出现这种情况,会报语法错误,请看下面的代码:
#include <stdio.h> int a = 10, b = 20, c; //错误:不能出现加减乘除运算 c = a + b; //错误:不能出现对其他函数的调用 printf("https://xiexuewu.github.io"); int main(){ return 0; }
总结
从整体上看,C语言代码就是由一个一个的函数构成的,除了定义和说明类的语句(例如变量定义、宏定义、类型定义等)可以放在函数外面,所有具有运算或逻辑处理能力的语句(例如加减乘除、if else、for、函数调用等)都要放在函数内部。在所有的函数中,main() 是入口函数,有且只能有一个,C语言程序就是从这里开始运行的。
C语言中所有的函数定义,包括主函数 main() 在内,都是平行的。也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数,被调用者称为被调函数。函数还可以自己调用自己,称为递归调用。
main() 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。因此,C程序的执行总是从 main() 函数开始,完成对其它函数的调用后再返回到 main() 函数,最后由 main() 函数结束整个程序。
声明:当前文章为本站“玩转C语言和数据结构”官方原创,由国家机构和地方版权局所签发的权威证书所保护。