【C语言】函数
目录
一、函数的概念
二、库函数
2.1 ❥ 标准库
2.2 ❥ 库函数的使用方法
三、自定义函数
四、形参和实参
4.1 ❥ 实参(实际参数)
4.2 ❥ 形参(形式参数)
五、return语句
六、函数的调用
6.1 ❥ 传值调用
6.2 ❥ 传址调用
七、函数的嵌套调用和链式访问
7.1 ❥ 嵌套调用
7.2 ❥ 链式访问
八、函数的声明和定义
8.1 ❥ 函数的声明
8.2 ❥ 函数的定义
一、函数的概念
函数(function)又称为:子程序。
C语言中的函数是一个完成某项特定任务的一小段代码,而且相较于其它代码,具备相对的独立性。
一般会有输入参数也有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
C语言的程序是由无数个小的函数组合而成的,也就是说:一个大的计算任务可以分解成若干个较小的函数(对应较小的任务)完成。同时一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。
在C语言中我们一般会见到两类函数:
- 库函数
- 自定义函数
二、库函数
为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
2.1 ❥ 标准库
C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数。
C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库。不同的编译器厂商根据ANSI提供的C语言标准就给出了一系列函数的实现,这些函数就被称为库函数。
我们学到的 printf 、scanf、pow都是库函数,库函数的也是函数,不过这些函数是现成的,我们只要学会就能直接使用了。有了库函数,⼀些常见的功能就不需要程序员自己实现了,一定程度提升了效率;同时库函数的质量和执行效率上都更有保证。
2.2 ❥ 库函数的使用方法
C/C++官方的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明。
库函数是在标准库中对应的头文件中声明的,所以使用库函数,必须包含#include对应的头文件。
例如sqrt的使用如下图:
三、自定义函数
自定义函数:是由程序员自己来创造的一个函数。
自定义函数和库函数一样,有函数名,返回值类型和函数参数。
语法形式如下:
ret_type fun_name(形式参数)
{
语句项;
}
- ret_type :函数返回类型
- fun_name :函数名
- 括号中放的是形式参数
- { }括起来的是函数体
我们可以把函数想象成小型的一个加工厂,工厂得输入原材料,经过工厂加工才能生产出产品,函数也是一样的,函数一般会输入一些值(可以是0个,也可以是多个),经过函数内的计算,得出结果。
- ret_type 用来表示函数计算结果的类型,当返回类型是 void的时候 ,表示什么都不返回。
- fun_name 是为了方便使用函数;就像人的名字⼀样,有了名字方便称呼,函数有了名字方便调用,所以函数名尽量要根据函数的功能起的有意义。
- 函数的参数就相当于,工厂中送进去的原材料,函数的参数也可以是 void ,明确表示函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
- {}括起来的部分被称为函数体,函数体就是完成计算的过程。
四、形参和实参
4.1 ❥ 实参(实际参数)
- 真实传给函数的参数,叫实参。
- 实参可以是:常量,变量,表达式,函数等。
- 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
- 实参是函数的前提是该函数一定得有返回值。
4.2 ❥ 形参(形式参数)
- 形式参数是指函数名后括号中的变量。
- 因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。
- 当函数调用完成之后形式参数就自动销毁了,因此形参只在函数中有效。
- 形式参数在函数不调用的情况下只是形式上的存在,只有在调用的时候才会真正的开辟内存空间。
- 形参实例化之后相当于实参的一份临时拷贝。
4.3 ❥ 举例
五、return语句
在函数的设计中,函数经常会出现return语句,那return语句的使用需要哪些注意事项呢?
- return后边可以是一个数值,也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式结果。
- return后边也可以什么都没有,直接写return; ,这种写法适合函数返回值类型是void的情况
- return返回的值和函数返回类型不一致,系统会自动将返回的值隐式转换为函数的返回类型。
- return语句执行后,函数就彻底返回,后边的代码不再执行。
- 如果函数中存在if等分支的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
六、函数的调用
6.1 ❥ 传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
例如下面代码:
#include void Swap(int x, int y) { int c = 0; c = x; x = y; y = c; } int main() { int a = 10; int b = 20; printf("交换前:a=%d,b=%d\n", a, b); Swap(a, b); printf("交换后:a=%d,b=%d\n", a, b); return 0; }
6.2 ❥ 传址调用
- 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
- 这种传参的方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
例如下面代码:
#include void Swap(int* x, int* y) { int c = 0; c = *x; *x = *y; *y = c; } int main() { int a = 10; int b = 20; printf("交换前:a=%d,b=%d\n", a, b); Swap(&a, &b); printf("交换后:a=%d,b=%d\n", a, b); return 0; }
6.3 ❥ 适用场景
那么什么时候需要&取地址什么时候不需要呢?
- 当对变量(实际参数)进行修改的时候,则需要取地址。
- 如果该函数只是想得到变量(实际参数)的值,则不需要取地址。
七、函数的嵌套调用和链式访问
7.1 ❥ 嵌套调用
嵌套调用就是:函数和函数之间根据实际需求互相调用。
注意事项
- 函数可以嵌套调用,但不能嵌套定义。
- 嵌套定义的意思:是函数里包含着另一个函数。
- 函数的地位都是平等的,都应该在同一位置上。
7.2 ❥ 链式访问
链式访问:就是将一个函数的返回值作为另外一个函数的参数,像链条一样将函数串起来就是函数
的链式访问。
我们看如下两段代码:
代码1
#include int main() { int len = strlen("abcdef"); printf("%d\n", len); printf("%d\n", strlen("abcdef")); //链式访问 return 0; }
代码2
出现该结果的原因是:printf函数返回的是打印在屏幕上的字符的个数。
上面例子中,第一个printf打印的是第二个printf的返回值,第二个printf打印的是第三个printf的返回值。
第三个printf打印43,在屏幕上打印2个字符,再返回2。
第二个printf打印2,在屏幕上打印1个字符,再返回1。
第一个printf打印1。
所以屏幕上最终打印:4321
注意事项
- 链式访问的前提是函数得有返回值
- 如果一个函数没有返回类型,返回类型写的void,那么就没法链式访问
- 函数不写返回值类型的时候,默认返回值类型是int
形参是void的意思是:明确说明不需要传参,传参就会报警告。如果没有void就不会报错。
如下图所示:
说明test有void参数,明确声明不要传参数。
八、函数的声明和定义
8.1 ❥ 函数的声明
我们先看下面这一段代码:
红色框住的是函数的定义,紫色框住的是函数的调用。
我们发现,通常在写函数的时候,会把函数的定义写在调用的前面,那如果写在后面呢?
我们来测试一下:
在vs2022上运行发现:代码会出现以上的警告信息。
原因:因为C语言编译器对源代码进行编译的时候,是从第一行往下扫描的,当遇见Add函数调用的时候,并没有发现前面有Add的定义,就会报出上述的警告。
如何才能解决这个问题,可以在函数调用之前先声明一下这个Add函数。声明函数只需要交代清楚函数名,函数的返回类型和函数的参数。
如:int Add(int x,int y);
这就是函数声明,函数声明中参数可以只保留类型,省略掉名字。
#include int Add(int a, int b);//函数的声明 int main() { int a = 10; int b = 20; int c = Add(a, b); return 0; } int Add(int a, int b) { return a + b; }
注意事项
- 函数的声明是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。
具体存不存在,函数声明决定不了,可能上面声明了这个函数,主函数也去使用了,但去找那个函数,却发现没有,(发布假的声明)。是否真的有还是取决于函数的定义。
- 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的定义也是一种特殊的声明,所以如果函数定义放在调用之前也是可以的。
多文件使用的时候,一般把函数的声明放在头文件(.h)中,函数的定义放在源文件(.c)中。
然后头文件就可以使用对应模块的代码。
注意:库里面提供的头文件,用尖括号;自己定义的头文件用双引号 " "。
这里用双引号定义的头文件就相当于函数的声明,其实就是为了实现函数的声明这个效果。它的本质意义就是把头文件内容拷过来。
函数的声明和函数的定义分文件的好处:
本来可以不用函数的声明,函数的定义利用函数的外部链接属性也可以实现,但分开写可以把函数的实现这个模块变成静态库,静态库是二进制的,是看不懂的。一个静态库里可以放很多函数,所以可以适当的隐藏代码,保证代码的隐私性。
- 逻辑清晰
- 方便多人协调
- 适当的隐藏代码
8.2 ❥ 函数的定义
是指函数的具体实现,交代函数的功能实现。
- 函数的声明是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。