文章目录
-
目录
[2.1 库函数](#2.1 库函数)
[2.2 自定义函数](#2.2 自定义函数)
[3.1 实参](#3.1 实参)
[3.2 形参](#3.2 形参)
[3.3 形参和实参的关系](#3.3 形参和实参的关系)
[4.1 传值调用](#4.1 传值调用)
[4.2 传址调用](#4.2 传址调用)
[5.1 函数的定义](#5.1 函数的定义)
[5.2 函数声明](#5.2 函数声明)
[6.1 嵌套调用](#6.1 嵌套调用)
[6.2 链式访问](#6.2 链式访问)
[7.1 递归的概念与思想](#7.1 递归的概念与思想)
[7.2 递归的限制条件](#7.2 递归的限制条件)
[7.3 递归的例子](#7.3 递归的例子)
前言
在C语言的世界里,函数不仅是代码组织的基本单元,更是程序员构建复杂系统的思维工具。从简单的 main()函数到庞大的库函数集合,函数贯穿了C语言的每个角落。
一、函数的概念
数学中我们其实就⻅过函数的概念,⽐如:⼀次函数 y=kx+b ,k和b都是常数,给⼀个任意的x,就得到⼀个y值。
而在C语言中,函数就是⼦程序, C语⾔中的程序就是⼀个又一个的子程序组合而成。
在项目中,⼀个⼤的计算任务可以分解成若⼲个较⼩的函数(对应较⼩的任务)完成。
并且在C语⾔中我们⼀般会⻅到两类函数:库函数,⾃定义函数
二、函数的分类
2.1 库函数
C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSIC规定了⼀ 些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。
我们前⾯内容中学到的 printf 、 scanf 都是库函数,库函数也是函数,不过这些函数已经是现成的,我们只要学会就能直接使⽤了。有了库函数,⼀些常⻅的功能就不需要程序员⾃⼰实现了,⼀ 定程度提升了效率;同时库函数的质量和执⾏效率上都更有保证。
那我们如何学会库函数的使用呢?下面有库函数的相关网址
库函数相关网址:cplusplus.com - The C++ Resources Network
在这网站中我们能查阅到库函数的基本信息,我们通过printf函数在cplusplus中的描述,看懂这些网站是怎么描述一个函数的,然后举一反三,更好的学习其他函数。

在这个界面中我们可以在上方Search中搜索我们想要认识的库函数,例如 printf 库函数

红色框为函数原型,我们可以从中了解到函数的参数类型,返回类型,这个非常重要,决定了我们要给函数传参的类型以及要用什么类型接收返回值。红色框往下就是这个函数的具体功能,如果看不懂我们可以打开翻译。

而且往下还有示范例子:

并且在左边还能看到这个库函数需要什么头文件

这个浅灰色的就是。
需要注意的是:库函数是在标准库中对应的头⽂件中声明的,所以库函数的使⽤,务必包含对应的头⽂件,不包含是可能会出现⼀些问题的。
库函数包含格式
cpp
#include<头文件>
2.2 自定义函数
重要的是自定义函数。
自定义函数和库函数一样,有函数名,返回值类型和函数参数。
所谓自定义就是这些都是我们自己来设计。这给我们自己一个很大的发挥空间。
⾃定义函数和库函数是⼀样的,语法形式如下:
cpp
ret_type fun_name(形式参数)
{
//ret_type是函数返回类型
//fun_name是函数名
// ()括号中放的是形式参数
//{}括起来的是函数体
}
再例如写一个加法函数的格式:
cpp
int add(int x,int y) {
return x + y;
}
三、函数的参数
在函数使⽤的过程中,把函数的参数分为,实参和形参。
看如下代码:
cpp
#include<stdio.h>
int add(int x,int y) {
return x + y;
}
int main()
{
int a = 3, b = 2;
int sum = 0;
sum = add(a, b);
printf("%d\n", sum);
return 0;
}
3.1 实参
实际参数就是真实传递给函数的参数。
在上述代码中,调⽤add函数时,传递给函数的参数a和b,称为实际参数,简称实参。
cpp
#include<stdio.h>
int add(int x,int y) {
return x + y;
}
int main()
{
int a = 3, b = 2;
int sum = 0;
sum = add(a, b); //a和b为实参
printf("%d\n", sum);
return 0;
}
3.2 形参
定义函数 add 的时候,在函数名 add 后的括号中写的 x 和 y ,称为形式参数,简称形参。
为什么叫形式参数呢?实际上,如果只是定义了 add函数,⽽不去调⽤的话,add函数的参数 x和y只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在 函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化。
cpp
#include<stdio.h>
int add(int x,int y) { //int x, int y 为形参
return x + y;
}
int main()
{
int a = 3, b = 2;
int sum = 0;
sum = add(a, b); //a和b为实参
printf("%d\n", sum);
return 0;
}
3.3 形参和实参的关系
实参是传递给形参的,他们之间是有联系的,但是形参和实参各⾃是独⽴的内存空间。
这个现象是可以通过调试来观察的。请看下⾯的代码和调试演⽰:
cpp
#include<stdio.h>
int add(int x,int y) {
int sum = 0;
sum = x + y;
return sum;
}
int main()
{
int a = 3, b = 2;
int sum = 0;
sum = add(a, b);
printf("%d\n", sum);
return 0;
}

我们在调试的可以观察到,x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样的,所以我们可以理解为形参是实参的⼀份临时拷⻉。
四、函数的调用
4.1 传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
例如以下交换函数代码:
cpp
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
Swap1(a, b);
printf("交换前:a = % d b = % d\n", a, b);
printf("交换后:a = % d b = % d\n", a, b);
return 0;
}
我们发现其实没产⽣交换的效果,这是为什么呢? 调试⼀下

我们发现在main函数内部,创建了a和b,a的地址是0x000000acfd18fc74,b的地址是0x000000acfd18fc94, 在调⽤ Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是**x的地址是0x000000acfd18fc50,y的地址0x000000acfd18fc58,**x和y确实接收到了a和b的值,不过x的地址和a的地址不 ⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap1函数内部交换x和y的值, ⾃然不会影响a和b,当Swap1函数调⽤结束后回到main函数,a和b的没法交换。Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这种叫传值调⽤。
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。所以Swap是失败的了。
4.2 传址调用
那我们应该怎么修改才可以让Swap函数达到我们想要实现的功能呢?
那这就需要我们的传址调用了,使⽤指针了,在main函数中将a和b的地址传递给Swap函数,Swap 函数⾥边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。
代码如下:
cpp
#include <stdio.h>
void Swap(int* px, int* py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a = % d b = % d\n", a, b);
Swap(&a, &b);
printf("交换后:a = % d b = % d\n", a, b);
return 0;
}
调试一下看看结果:

我们发现现在a和px的地址,b和py的地址就相同了,那么就可以交换地址以达到交换值的效果了,这就是传址调用。
五、函数的声明和定义
5.1 函数的定义
函数的定义是指函数的具体实现,交待函数的功能实现。
如下,下面整个代码都是add函数的定义。
cpp
int add(int x, int y) {
int sum = 0;
sum = x + y;
return sum;
}
我们已经定义好了add函数,那我们如何调用它才不会出错呢?
这就需要我们函数的声明了。
5.2 函数声明
函数声明就是告诉编译器函数的接口信息,让编译器提前知道有这个函数存在。
函数的声明一般出现在函数的使用之前。要满足先声明后使用。
特别注意的是:函数定义在调用这个函数前的话可以当做特殊声明,如下:
cpp
#include <stdio.h>
int add(int x, int y) {
int sum = 0;
sum = x + y;
return sum;
}
int main()
{
int a = 3, b = 2;
int sum = 0;
sum = add(a, b);
printf("%d\n", sum);
return 0;
}
我们也可以提前声明add函数让编译器知道有这个函数存在。
cpp
#include <stdio.h>
int add(int x, int y); //函数声明
int main()
{
int a = 3, b = 2;
int sum = 0;
sum = add(a, b);
printf("%d\n", sum);
return 0;
}
int add(int x, int y) { //函数定义
int sum = 0;
sum = x + y;
return sum;
}
六、嵌套调用和链式访问
6.1 嵌套调用
嵌套调⽤就是函数之间的互相调⽤。
如下代码:
cpp
void fun() {
printf("Hello\n");
funn();
return;
}
void funn() {
printf("你好\n");
return;
}
int main() {
fun();
return 0;
}
main函数调用fun函数打印 Hello ,fun函数又调用funn函数打印 你好 。
这就是函数的嵌套调用。
6.2 链式访问
所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。
⽐如:
cpp
#include <stdio.h>
#include<string.h>
int main()
{
int len = strlen("abcdef");//1.strlen求⼀个字符串的⻓度
printf("%d\n", len);//2.打印⻓度
return 0;
}
前⾯的代码完成动作写了2条语句,把如果把strlen的返回值直接作为printf函数的参数呢?这样就是⼀ 个链式访问的例⼦了。
cpp
#include <stdio.h>
#include<string.h>
int main()
{
printf("%d\n", strlen("abcdef"));//链式访问
return 0;
}
七、函数递归
7.1 递归的概念与思想
在C语⾔中,函数递归就是函数⾃⼰调⽤⾃⼰。
递归的思想:把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再 被拆分,递归就结束了。所以递归的思考⽅式就是把⼤事化⼩的过程。
递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会。
7.2 递归的限制条件
递归在书写的时候,有2个必要条件:
递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。
每次递归调⽤之后越来越接近这个限制条件。
7.3 递归的例子
求n的阶乘
代码如下:
cpp
#include <stdio.h>
int Fact(int n)
{
if (n <= 0)
return 1;
else
return n * Fact(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fact(n);
printf("%d\n", ret);
return 0;
}
我们输入5,得到的结果为120

画图演示一下递归的流程:
