暂时无法在飞书文档外展示此内容
1、函数背景引入(可略)
1.1、 数学中的函数 与 C 语言中的函数
数学中我们知道函数的概念,如:⼀次函数 y=kx+b , k 和 b 都是常数,给⼀个任意的 x,y 就得到⼀个值。
- 数学中有 y = f(x)
- C 语言中有 y = sizeof(x);
你看它们是何其相似,都是通过一定的操作或规则,由一份数据得到另一份数据。
总结就是:
C 语⾔中的函数
就是完成某项特定的功能的⼀小段代码
。一个程序
由多个函数
组成,可以理解为「一个程序由多个小的功能叠加而成」
。
1.2、使用函数的优势
C 语言提供了很多功能,例如输入输出、获得日期时间、文件操作等,我们只需要一句简单的代码就能够使用。但是这些功能的底层都比较复杂,通常是软件和硬件的结合,还要要考虑很多细节和边界,如果将这些功能都交给程序员去完成,那将极大增加程序员的学习成本,降低编程效率。 好在 C 语言的开发者们为我们做了一件好事,他们已经编写了大量代码,将常见的基本功能都完成了,我们可以直接拿来使用。但是现在问题来了,那么多代码,如何从中找到自己需要的呢?一股脑将所有代码都拿来显然是非常不明智的。 这些代码,早已被分门别类地放在了不同的文件中,并且每一段代码都有唯一的名字。使用代码时,只要在对应的名字后面加上 ( )
就可以。这样的一段代码能够独立地完成某个功能,一次编写完成后可以重复使用。
- 读者可以认为,函数就是一段可以重复使用的代码。
总结就是两点:
- 分解任务:C 语⾔的程序其实是由⽆数个小的函数组合⽽成的,也可以说:使用函数可以把大的计算任务分解成若干个较小的函数(对应较小的任务)完成。
- 代码复用:⼀个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。
1.3、函数的分类
在 C 语⾔中我们⼀般会见到两类函数:
1.3.1、库函数
C 语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,可以简单地认为它是一些列函数的集合,在磁盘上往往是一个文件夹。C 语言自带的库称为标准库(Standard Library),其他公司或个人开发的库称为第三方库(Third-Party Library)。
我们前⾯内容中学到的 printf 、 scanf 、 getchar 、 putchar 等都是库函数。
库函数的也是函数,不过这些函数已经是现成的,我们只要学会使用就能直接使用了,只要包含函数需要的头⽂件即可。
1.3.2、自定义函数
库函数再好,提供的功能还是有限的;日常写代码的需求是千变万化的,所以还是需要程序员写各种各样的代码,这些代码也是由⼀个个函数组成。
因此,我们自己设计和实现的函数,称为自定义函数
,main 函数
也算是自定义函数
。
2、函数概念巧记!
- 在 C 语言中,有的语句使用时不能带括号,有的语句必须带括号
( )
。
我们可以这样认为,其中带括号的语句称为 函数(Function)。
- 函数的一个明显特征就是
使用时必须带括号
( )
,
如果函数有参数的话,括号中还可以包含待处理的形参数据。
例如:
printf("Hello, World! \n");
使用了一段具有输出功能的代码,这段代码的
函数名字
是printf
,"Hello, World! \n"
是要交给这段代码处理的形参数据
。
- 如果函数需要处理
多个数据
,那么它们之间使用逗号,
分隔。
例如:
pow(10, 2); // 该函数用来求10的2次方
- 函数的另一个明显特征就是
一组一起执行一个任务的语句,包含在
{}
里。
{} 之间的代码是
函数要实现的功能
。
{} 之间的代码也是
函数的作用域
。
-
使用函数
在编程中有专业的称呼,叫做函数调用
(Function Call)。后面会再解释。 -
接下来我们就可以来了解
函数的定义
!
3、函数的定义
3.1、C 语言中的函数定义的一般形式
return_type function_name ( [parameter list] )
{
body of the function
}
也就是:
返回类型 函数名 ( [参数列表] )
{
函数主体
}
[ ]
说明此参数是可省略的。
如之前的文章(2.2)基本程序结构中的 C 实例 -- Hello World
就是一个标准的函数定义模版。如下:
#include <stdio.h> //头文件
//空白行
/* 我们的第一个 C 程序 */ //注释
int main() //函数
{ //函数体开始
printf("Hello, World! \n"); //输出语句
return 0; //结束并返回语句
} //函数体结束
在 C 语言中,
函数
由一个函数头
和一个函数主体
组成。
下面列出一个函数的所有组成部分:
3.2、返回类型
一个函数可以返回一个值
或者 不返回
。
3.2.1、函数的返回值
- **
函数的返回值
**是指函数被调用之后,执行函数体中的代码所得到的执行结果
,并且这个结果通过return
语句返回。
例如:
size_t len = sizeof(int);
解释说明:
sizeof(int) 的处理结果是 size_t
的字节长度
,我们通过 len 变量来接收。
3.2.2、返回类型
返回类型
是使用函数后,返回值
的
数据类型
。
函数返回值有固定的数据类型(int、char、float 等),用来接收返回值的变量类型要一致。
例如:
size_t len = sizeof(int);
解释说明:
sizeof(int) 的返回值的变量类型是 size_t
,所以 len 变量也是 size_t
。
3.2.3、return 的用法详解
return 语句的一般形式为:
return 表达式;
或者:
return (表达式);
有没有( )
都是正确的,为了简明,一般也不写( )
。例如:
return max;
return (100+200);
对 C 语言返回值的说明:
- 没有返回值的函数为空类型,在这种情况下,
return_type
是关键字void
,用void
表示。例如:
void func() {
printf("bobo study! \n");
}
一旦函数的返回值类型被定义为 void,就不能再接收它的值了。
为了使程序有良好的可读性并减少出错, 凡
不要求返回值的函数
都应定义为void 类型
。
- 同一个函数里 return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值(少数的编程语言支持多个返回值,例如 Go 语言)。例如:
int func() {
printf("bobo \n");
return 0;
printf("study! \n");
}
此时输出的内容为 bobo
,为什么,马上看以下第三点!
- 函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,
return
语句还有强制结束函数执行
的作用。
例如:
int func() {
printf("bobo \n");
return 1;
printf("study! \n");
return 2;
}
第 4 行和第 5 行代码就是多余的,永远没有执行的机会。
return 语句是提前结束函数的唯一办法。
总之:
return 后面可以跟一份数据,表示将这份数据返回到函数外面;return 后面也可以不跟任何数据,表示什么也不返回,仅仅用来结束函数。
3.3、函数名称
这是函数的实际名称,一个合法的用户自定义标识符,函数名尽量要根据函数的功能起的有意义。
看底下章节的例子:3.6、函数头的定义举例
3.4、参数列表
参数就像是占位符,当函数被调用时,就会向函数传递一个值。
参数列表和返回类型可以简单理解为输入和输出,参数列表传参进来,返回类型返回值出去。
3.4.1、参数列表基本形式
type1 param1, type2 param2, type3 param3 ...
是参数列表。
相当于
类型1 变量名1,类型2 变量名2,类型3 变量名3,...
是参数列表。
3.4.2、函数的参数
函数的参数是可选的
,也就是说:
- 函数可能
不包含参数
,但括号不可省略; - 也可以
只有一个参数
; - 也可以
有多个
,多个参数之间由,
分隔。
参数本质上也是变量,定义时要指明类型和名称,使用方式和普通变量一样。
与无参函数的定义相比,有参函数的定义仅仅是多了一个参数列表。
3.4.3、形参和实参简单简介
- 函数定义时给出的参数称为形式参数,简称形参;
- 函数调用时给出的参数(也就是传递的数据)称为实际参数,简称实参。函数调用时,将实参的值传递给形参,相当于一次赋值操作。
原则上讲,实参的类型和数目要与形参保持一致。如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。
(强转以后会讲解)
总结:
数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。
3.5、函数主体
函数主体包含一组定义函数执行任务的语句,实现特定的算法或功能。
有可能函数体内无语句
,空的函数体便于以后扩充。- 函数体内不可以出现其他函数的定义,即
函数不能嵌套定义
(底下章节**6、函数不能嵌套定义
**)。
- 若返回值类型
不是
void 类型,则函数体内一定要配合使用return 表达式;
语句。 - 若返回值类型
是
void,则在需要返回的点使用return ;
语句,建议
编程者采用这种方式,也可以没有 return 语句,则执行到函数体的右大括号结束。
3.6、函数头的定义举例:
函数的功能描述 | 函数定义的首部 |
---|---|
求两个整数之间的较大值 | int FindMax ( int x, int y) |
求两个实型数的和 | double Add ( double a, double b) |
判断一个整数是否为质数 | int JudgePrime ( int n ) |
画出一条由 30 个减号组成的横线 | void DrawLine( ) |
求两个整数的最大公约数 | int Gcd ( int a , int b) |
统计出 m 到 n 之间的水仙花数 | int CountNarcissus ( int m , int n ) |
求出 m 到 n 之间所有的同时能被 5 和 7 整除的奇数和 | int SumOdd ( int m , int n ) |
4、函数调用
创建 C 函数时,会定义函数做什么,然后通过调用函数来完成已定义的任务。
也就是说:
函数调用
(Function Call),就是使用已经定义好的函数。
函数调用的一般形式为:
functionName (param1, param2, param3 ...);
functionName 是函数名称,param1, param2, param3 ...
是实参列表。实参可以是常数、变量、表达式等,多个实参用逗号,
分隔。
调用过程文字理解:
当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。(具体例子请看章节**5、函数先定义再使用
**)
如下例子: 在 C 语言中,函数调用的方式有多种,例如:
- 函数作为一个单独的语句。
printf("%d", a);
scanf("%d", &b);
- 函数作为表达式中的一项出现在表达式中。
z = max(x, y);
m = n + max(x, y);
- 函数作为调用另一个函数时的实参。
printf( "%d", max(x, y) );
total( max(x, y), min(m, n) );
5、函数先定义再使用
定义一个函数是为了调用,
函数调用有两种类型
。
- 一是
先定义,后调用
,这要求函数定义和调用语句在同一个文件内,编译器能从函数定义中提取函数的参数列表、输出类型等接口信息。 - 二是
函数声明+函数调用
,大多数情况下,函数的定义与函数的调用并不在一个文件内,即使在一个文件中也有可能调用在前而定义在后,这时需要在调用之前先对函数声明,告诉编译器有这么一个函数存在。函数原型声明将在后面讨论。
下面我们来写一个先定义、后调用的例子:
写⼀个加法函数,完成 2 个整型变量的加法的代码,并输出最后的值!
- 先写一个基本框架
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
int sum = 0;
scanf("%d %d", &a, &b);
printf("%d\n", sum);
return 0;
}
解释说明:
- scanf 函数输入变量 a 和 变量 b 的值;
- printf 函数输出最终的和。
- 我们根据要完成的功能,给子函数取名:Add,函数 Add 需要接收 2 个整型类型的参数,函数计算的结果也是整型。
int Add(int x, int y)
{
int sum = 0;
sum = x+y;
return sum;
}
解释说明:
- 把传参进来的两个变量相加,赋值给 sum 变量;
- 得到相加的结构,通过 return 语句返回。
- 合并两个功能代码,并在 main 函数里调用 Add 函数。
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int a = 0;
int b = 0;
int sum = 0;
scanf("%d %d", &a, &b);
sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
解释说明:
- main 函数里调用 Add 函数,Add 函数就会执行相加两个变量的参数;
- Add 函数返回相加后的值给 sum 变量,再打印出来,return main 函数结束。
6、函数不能嵌套定义
强调一点,C 语言不允许函数嵌套定义
:也就是说,不能在一个函数中定义另外一个函数,必须在所有函数之外定义另外一个函数。
main() 也是一个函数定义,也不能在 main() 函数内部定义新函数。
下面的例子是错误的:
#include <stdio.h>
void func1(){
printf("bobo study! \n");
void func2(){
printf("C语言入门教程\n");
}
}
这是错误的,编译时将会报错。
正确的写法应该是这样的:
#include <stdio.h>
void func2(){
printf("C语言入门教程\n");
}
void func1(){
printf("bobo study! \n");
}
func1()、func2()两个函数是平行的,谁也不能位于谁的内部。
- 有些编程语言是允许函数嵌套定义的,例如 JavaScript,在 JavaScript 中经常会使用函数的嵌套定义。
7、函数签名
函数名和参数列表一起构成了函数签名。
函数名和参数列表一起构成了函数签名。意味着可以出现参数列表不同但是函数名相同的函数。比如说定义两个函数:
void print()
{
printf("bobo study! ");
}
void print(int nums)
{
printf("%d\n", a);
}
这是合法的。
8、函数定义的设计
如果我们需要实现⼀个函数来解决问题,我们是需要根据实际的场景,来分析函数是否需要参数
,需要几个参数
,是否需要返回值
,返回什么类型的值
。等这些确定好后,再给⼀个函数确定⼀个有意义的名字,就可以实现这个函数了。
每日一更!
公众号、CSDN等博客:小菜狗编程笔记
谢谢点赞关注哈!目前在飞书持续优化更新~
日更较慢有需要完整笔记请私我,C/C++/数据结构-算法/单片机51-STM32-GD32-ESP32/嵌入式/Linux操作系统/uboot/Linux内核-驱动-应用/硬件入门-PCB-layout/Python/后期小程序和机器学习!