✅作者简介:大家好我是zoroxs
📃个人主页:c/c++学习之路
🔥💖如果觉得博主的文章还不错的话,请👍三连支持一下博主哦
目录
1.函数是什么
先来给大家说说什么是函数,不同于数学中的函数。
函数:
- 函数被称为子程序
- 子程序是一个大型程序中的某部分代码, 由一个或多个语句块组成。
- 它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性
- 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
简单来说呢,函数就是可以完成某种特定任务,而又相对独立的一个代码块。
在C语言中,子程序就是由若干个函数组成的,大家肯定认识一个叫main函数的东西
在C语言中必不可少,由它来调用C语言中的其他函数完成程序的功能
2.库函数
C语言为了方便用户使用,提供了大量的库函数,比如大家常用的printf
C语言中的库函数还有很多,大致分为一下几类
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
例如常见的: printf 、 scanf 、 strcmp 、malloc 、 time 、sqrt …
这里就不列举了,有一个一定要注意的地方:
使用库函数,一定要包含头文件
大家没必要记住所有的库函数,需要使用的时候去文档里查
这里给大家推荐三种查询的方式
- www.cplusplus.com —>一个c/c++的查询网站,非常方便
- http://zh.cppreference.com/ —>c++官网,在国外,访问速度特别慢
- MSDN ---->一个IDE工具,挺方便的,找不见的可以私信我安装包
使用库函数的过程比较简单,照着文档使用即可
3.自定义函数
如果库函数能干所有的事情,那还要程序员干什么?
对于我们而言,如何自定义函数是更加重要的
自定义函数和库函数一样,有函数名,返回值类型和函数参数。,但是都由我们来设计
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
来举一个例子: 实现一个函数,求两个数的和
我们来思考一下:
我们要让这个函数来计算两个数的和,那我们肯定要给它两个数,不然它算啥?
然后,它肯定要把计算的结果给我们,这就是函数参数与返回值
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main(void)
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int sum = Add(a, b);
printf("sum = %d\n",sum);
}
这就是一个最简单的自定义函数了。
我们在设计函数的时候,一定要想清楚,我要让这个函数去做什么,我应该给它什么,它应该给我什么
我们再来看另外一个例子
写一个函数可以交换两个整形变量的内容
我们继续再来分析一下:
我们要让它交换两个变量的值,那我们肯定要给它两个变量 ,然后它需要给我们什么呢?
好像并不需要,它只需要把我们给它的两个值交换了就可以了
所以我们的设计:
参数: 两个整形
返回值: 无
#include <stdio.h>
void Swap(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int num1 = 1;
int num2 = 2;
printf("交换前: num1 = %d num2 = %d\n", num1, num2);
Swap(num1, num2);
printf("交换后: num1 = %d num2 = %d\n", num1, num2);
return 0;
}
为什么这个函数没达到我们的要求呢????
我们来调试一下
这里我们看到,x、y确实被交换,但是num1和num2并无变化, 所以我们如何修改才能让swap函数影响外边的num1 和num2呢?
我们可以通过它们的地址找到它们,把它们修改掉,我们来试一下
涉及到指针最简单的知识,我在初识C语言简单介绍了一下,不了解的可以去看初识C语言
#include <stdio.h>
void Swap(int *x, int *y)
{
int tmp = 0;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int num1 = 1;
int num2 = 2;
printf("交换前: num1 = %d num2 = %d\n", num1, num2);
Swap(&num1, &num2);
printf("交换后: num1 = %d num2 = %d\n", num1, num2);
return 0;
}
来看结果
大家搞清楚自己的需求,就可以很好地使用函数
4.函数参数
我们来看一下函数的参数
4.1形参(形式参数)
- 形式参数是指函数名后括号中的变量
- 形式参数只有在函数被调用的过程中才实例化(分配内存单元)
- 当函数调用完成,形参销毁,所以形参只在函数内有效
4.2实参(实际参数)
- 真实传给函数的参数,叫实参。
- 实参可以是:常量、变量、表达式、函数等
- 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
在上一个例子中,x,y无论是指针变量还是普通变量,都是形式参数,而传入的num1、num2、&num1、&num2都是实际参数
可以看出,实参和形参的地址并不相同,形参实例化之后其实相当于实参的一份临时拷贝。
5.函数调用
5.1传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
5.2传址调用
- 把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
- 可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量
通过上边那个交换数字的案例,相信大家对传值还是传址有了深刻的认知
如果我们在函数内部需要修改外边的变量,就要使用传址调用
6.函数的嵌套调用和链式访问
6.1嵌套调用
函数可以嵌套调用,但是不能嵌套定义
eg:
#include <stdio.h>
//这样称为嵌套调用
void test1()
{
printf("我是test1\n");
test2();
}
void test2()
{
printf("我是test2\n);
}
int main(void)
{
test1();
return 0;
}
//嵌套定义非法
//void test3()
//{
// void test4(){
// }
//}
6.2函数的链式访问
链式访问就是指把一个函数的返回值作为另一个函数的参数
eg:
#include <stdio.h>
int main(){
printf("%d", printf("%d", printf("%d", 43)));
}
这个代码的结果是啥呢?
首先我们要知道printf的返回值是什么
所以我们分析一下结果,最后面一个printf打印43,返回2,然后又打印2,返回1,然后又打印1
所以结果应该是4321,我们来看看对不对
7.函数的声明和定义
函数的声明,其实就是告诉编译器有一个函数,叫啥,参数是啥,返回值是啥类型
- 函数的声明一般出现在函数调用之前
- 函数的声明一般要放在头文件中
函数的定义:
函数的定义是指函数的功能实现。
举个栗子:
#include <stdio.h>
//可以这样声明
int Add(int x, int y);
//也可以只写类型
int Add(int, int );
int main(void)
{
//也可以在这里声明
int Add(int, int);
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("%d\n", Add(a, b));
return 0;
}
//这里是函数的具体实现,也叫函数定义哦
int Add(int x,int y)
{
return x + y;
}
8.函数递归
8.1什么是递归
- 程序调用自身的编程技巧称为递归。 --> 简单来说,函数自己调用自己被称为递归
- 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法
- 通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归最重要的思考方式就是: **大事化小 **
8.2递归的必要条件
递归有两个必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件
8.2.1画图详解案例–>打印数字问题
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4.
把我们的想法用代码实现一下
#include <stdio.h>
void print(int n)
{
//这是第一个递归必须提交,
if(n < 10) //如果n只有一位数
{
printf("%d ", n % 10);
}
else
{
print(n / 10);//第二个递归必要条件
printf("%d ", n % 10);
}
}
int main()
{
int num = 1234;
print(num);
return 0;
}
再来分析一波
这就是第一个递归的例子,大家一定要自己画图去理解
8.2.2 画图详解案例–>不使用临时变量求字符串长度
注:字符串的长度是指’\0’之前的所有字符
把我们的想法用代码实现
#incude <stdio.h>
int my_strlen(const char*str)
{
if(*str == '\0')//第一个必需条件
return 0;
else
return 1+my_strlen(str+1); //str+1是第二个必需条件
}
int main()
{
char str[] = "abc";
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
再把具体的执行流程分析一下就理解了
关于递归,大家一定要多画图理解
把握住递归核心思想 —> 大事化小
别忘记递归的两个必要条件
8.3递归与迭代
求n的阶乘
n的阶乘有一个数学公式
所以我们直接使用递归来写
#include <stdio.h>
int factorial(int n)
{
if (n <= 1)
{
return 1;
}
else
{
return n * factorial(n - 1);
}
}
int main(void)
{
int n = 0;
scanf("%d", &n);
int ret = factorial(n);
printf("%d\n", ret);
return 0;
}
我们也可以采用迭代的方式来写,大家自己实现,想不到的可以私我
我们再来看一个问题
求第n个斐波那契数。(不考虑溢出)
斐波那契数列是解决兔子问题的,前两个数为1,后边的其他数等于前两个数之和
我们使用递归来解决
#include <stdio.h>
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
int main(void)
{
int n = 0;
scanf("%d",&n);
int ret = fib(n);
printf("%d",ret);
return 0;
}
这个代码可以轻松地求出前边的斐波那契数,可是发现一个问题
当计算第50个斐波那契数字的时候特别耗费时间,计算第100个(不考虑溢出)会奔溃
这是为什么呢?
计算量呈指数增长,而且都是重复性的计算
我们来把代码调整一下
可以看到,求第40个斐波那契数列,fib(3)重复计算了将近4000万次,可想而知
我们可以采用迭代的方法来实现这个代码
#include <stdio.h>
int fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while(n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main(void)
{
int n = 0;
scanf("%d",&n);
int ret = fib(n);
printf("%d",ret);
return 0;
}
什么时候使用递归,什么时候迭代,值得我们深思,寻找适合的方法,解决对应的问题
总结
在C语言中,函数是非常重要的功能,我们一定要学会如何使用自定义函数,用来解决我们的需求。
函数的封装可以使得我们的程序更加简洁,更加模块化。
如果本文对您有所帮助的话,请👍三连支持一下博主哦