C语言之【函数】篇章以及例题分析
温馨提示:这篇文章已超过449天没有更新,请注意相关的内容是否还可用!
文章目录
- 前言
- 一、函数是什么?
- 二、C语言中函数的分类
- 1、库函数
- 2、自定义函数
- 三、函数的参数
- 1、实际参数(实参)
- 2、形式参数(形参)
- 四、函数的调用
- 1、传值调用
- 2、传址调用
- 3、专项练习
- 3.1 素数判断
- 3.2 闰年判断
- 3.3 二分查找
- 3.4 修改数值
- 五、函数的嵌套调用和链式访问
- 1、嵌套调用
- 2、链式访问
- 六、函数的声明和定义
- 1、函数声明
- 2、函数定义
- 七、函数递归
- 1、什么是函数递归?
- 2、函数递归的两个必要条件
- 3、题目的展开与剖析
- 3.1 例题1:打印数字
- 3.2 例题2:求字符串长度
- 3.3 例题3:阶乘求解
- 3.3 例题4:斐波那契数列
- 八、函数栈帧的创建的销毁
前言
在C语言中,这个函数时必不可少的,没有函数没有灵魂,要不然代码就会乱成一团,所以我们要学函数,接下来就开始函数之旅~~
一、函数是什么?
-
数学中我们其实就见过函数的概念,比如:一次函数y=kx+b ,k和b都是常数,给一个任意的x,就得到一个y值。其实在C语言也引入函数(function) 的概念,有些翻译为:子程序,子程序这种翻译更加准确一些。C语言中的函数就是一个完成某项特定的任务的一小段代码。
-
这段代码是有特殊的写法和调用方法的。C语言的程序其实是由无数个小的函数组合而成的,也可以说:一个大的计算任务可以分解成若干个较小的函数(对应较小的任务)完成。同时一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。
-
在C语言中我们一般会见到两类函数:
- 库函数
- 自定义函数
二、C语言中函数的分类
1、库函数
- C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库,那不同的编译器厂商根据ANSI提供的C语言标准就给出了一系列函数的实现。这些函数就被称为库函数。
我们前面内容中学到的printf 、scanf 都是库函数,库函数的也是函数,不过这些函数已经是现成的,我们只要学会就能直接使用了。有了库函数,一些常见的功能就不需要程序员自己实现了,一定程度提升了效率;同时库函数的质量和执行效率上都更有保证。
- 各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明。
- 库函数相关头文件:https://zh.cppreference.com/w/c/header
有数学相关的,有字符串相关的,有日期相关的等,每一个头文件中都包含了,相关的函数和类型等信息,库函数的学习不用着急一次性全部学会,慢慢学习,各个击破就行。
- C语言常用的库函数都有:
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
接下来,我会参照文档,给大家将两个常用的库函数,来教会大家如何入阅读英文文档
- 首先我们看函数原形:
strcpy
char * strcpy ( char * destination, const char * source );
- 再到代码中来看看它的实际应用场景,在使用这个函数的时候需要引入头文件 【string.h】
- 那么怎么看一个库函数改引入什么头文件呢?可以打开上面那个网站,搜索库函数,然后在左面就会有
#include #include int main() { char arr1[] = "###########"; char arr2[] = "hello word"; //strcpy在拷贝的时候'\0'也会被拷贝过来 strcpy(arr1, arr2); printf("%s", arr1); return 0; }- 再来看看运行结果
接下去再来看一库函数【memset】
memset
void * memset ( void * ptr, int value, size_t num );
- 一样来看一下代码该如何书写
char arr[] = "hello bit"; memset(arr, 'x', 5); printf("%s\n", arr);- 来看一下上面这段代码的执行结果。
注:
但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。
这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。
如何学会使用库函数?
需要全部记住吗?
No 需要学会查询工具的使用:
MSDN(Microsoft Developer Network)
en.cppreference.com【英文版】
zh.cppreference.com【中文版】
2、自定义函数
- 如果库函数能干所有的事情,那还要程序员干什么?
- 所以更加重要的是【自定义函数】
- 自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间
函数的组成:
ret_type fun_name(para1, * ) { statement;//语句项 }- ret_type 返回类型
- fun_name 函数名
- para1 函数参数
我们首先来举一个例子:
写一个函数可以找出两个整数中的最大值。
int Get_Max(int x, int y) { return (x > y ? x : y); } int main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); int max = Get_Max(a, b); printf("二者中的较大值为:%d\n", max); return 0; }-
然后给大家讲解一下这个函数
-
接下去我们再来举一个例子,也是函数这一块最经典的案例:【数值交换】
-
将交换两个数这个逻辑单独封装成了一个函数,因为它是作为一个功能出现的
void swap(int x, int y) { int t = x; x = y; y = t; } 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; }- 来看一下运行结果。可以看到两个数并没有发生交换
- 那有小伙伴就很诧异,这是为什么呢?我们一起来调试分析一下
结论: 形参实例化之后其实相当于实参的一份临时拷贝
-
上面这一个,叫做【传值调用】,函数内部形参的修改是不会影响实参的,接下来我们来讲讲【传址调用】~~
-
先看一下代码,然后我们再通过DeBug来展开分析一下
void swap(int* px, int* py) { int t = *px; *px = *py; *py = t; }- 最后来看一下运行结果,已经交换成功了~~
三、函数的参数
1、实际参数(实参)
-
真实传给函数的参数,叫实参
-
对于实参而言,它可以是【常量】、【变量】、【表达式】、【函数】等。我们到VS里来测试一下
-
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参
2、形式参数(形参)
- 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
所以我们可以总结出来一句话:
函数调用时,实参传递给形参,形参是实参的一份临时拷贝,形参的改变不影响实参
四、函数的调用
- 这里我们再来说一下函数的【传值调用】和【传址调用】
1、传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
2、传址调用
- 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
- 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
3、专项练习
3.1 素数判断
- 【素数】又叫质数。素数,指的是大于1的整数中,只能被1和这个数本身整除的数
- 知道了规则,那代码就好写了。我们来输出一下100~200的所有素数。在外部写一个循环,就可以获取100 - 200之间的数字了,然后对于素数的求解,可以单独封装为一个函数
- 首先来思考需要传入哪些参数,很明显:只需要传入被判断的数字即可,然后就要去思考这个函数的返回值,可以这样设定:在函数内部若是判断出其为素数,那么就返回1,否则就返回0,然后在主函数外部进行一个判断即可
int main() { for (int i = 100; i if (IsPrime(i) == 1) { printf("%d是素数\n", i); } } return 0; } int j = 0; for (int j = 2; j运行结果如下
- 我们通过递归展开图再来看看~~
3.2 例题2:求字符串长度
-
【要求】:输入abc,输出其长度为3
-
上面有讲到过strlen()这个函数,其可以求出一个字符串的长度,我们首先通过这个函数来试试
int main() { char str[] = "abc"; int len = strlen(str); //利用库函数进行求解 printf("len = %d\n", len); return 0; }- 接下去的话我们将这个strlen()函数改成my_strlen(),我们自己来实现一下这个底层的逻辑。
- 对于int len = my_strlen(str);,我们传入了字符数组str的首元素地址,那上面有讲到过对于地址要使用指针来进行接收,最后还要返回求出的长度,所以对于函数我们可以定义成这样
int my_strlen(char* str)
-
对于str这个字符指针现在是指向传入字符数组的首元素地址,也就是【a】,【\0】是一个字符串的结束标志,可以让这个字符指针不断后移,每一次对其进行解引用看看是否为【\0】即可,有了思路我们就可以写出代码了
-
在这里的话还需要设置计数器,我们我们要去求解这个字符串的长度,因此判断到它不为【\0】的时候就count++,然后让这个字符指针进行后移即可。
int my_strlen(char* str) { int count = 0; while ((*str) != '\0') { count++; //计数器累加 str++; //字符指针后移 } return count; }- 那我们现在的要求是不可以使用计数器进行求解字符串的长度
- 一样,我们可以使用到上面的分割思想
- 对于my_strlen(abc)我们可以拆成1 + my_strlen(bc)
- 对于my_strlen(bc)我们可以拆成1 + my_strlen(c)
- 对于my_strlen(c)我们可以拆成1 + my_strlen('\0')
- 那my_strlen(abc)就相当于是1 + 1 + 1 + 0。也是使用字符指针去进行一个后移,若其不为【\0】时,就不断对这个字符串进行拆分,然后直到遇到【\0】时便return 0。接下去就可以写出代码了
int my_strlen(char* str) { if (*str != '\0') { return 1 + my_strlen(str + 1); } return 0; }
-
-
- 我们通过递归展开图再来看看~~
- 首先来思考需要传入哪些参数,很明显:只需要传入被判断的数字即可,然后就要去思考这个函数的返回值,可以这样设定:在函数内部若是判断出其为素数,那么就返回1,否则就返回0,然后在主函数外部进行一个判断即可
- 这里我们再来说一下函数的【传值调用】和【传址调用】
- 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
-
- 最后来看一下运行结果,已经交换成功了~~
-
- 那有小伙伴就很诧异,这是为什么呢?我们一起来调试分析一下
- 来看一下运行结果。可以看到两个数并没有发生交换
-
- 来看一下上面这段代码的执行结果。
- 一样来看一下代码该如何书写
- 再来看看运行结果
- 首先我们看函数原形:
- C语言常用的库函数都有:
- C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库,那不同的编译器厂商根据ANSI提供的C语言标准就给出了一系列函数的实现。这些函数就被称为库函数。
-






















