一、函数的参数
1、实际参数(实参)
真实传给函数的参数,叫做实参。
实参可以是:常量、变量、表达式、函数等。、
无论实参是何种类型的变量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
2、形式参数(形参)
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
3、举例
写一个函数交换两个变量的内容
#include <stdio.h>
//*x *y
//通过解引用找到实参a b的地址
//形参有自己独立的空间地址
void exchange2(int* x,int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
void exchange1(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
int main()
{
int a = 2;
int b = 5;
printf("交换前的 a=%d b=%d\n", a, b);
exchange1(a, b);
printf("exchange1交换后的 a=%d b=%d\n", a, b);
exchange2(&a,&b);
printf("exchange2交换后的 a=%d b=%d\n", a, b);
return 0;
}
exchange1和exchange2函数中的参数x,y都是形式参数,只是类型不一样。
运行结果:
二、函数的调用
1、传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
exchange1函数调用的时候,x,y就拥有了自己的空间,同时拥有了和实参一模一样的内容。所以可以简单的认为:形参就是实参的一份临时拷贝。
2、传址调用
传址调用是把函数外部创建的内存地址传递给函数参数的一种方式。
这种传参方式可以让函数和函数外部变量建立起真正的联系,也就是函数内部可以直接操作函数外部变量。
exchange2函数是将a,b的地址传给了形参x,y,通过解引用找到了在内存中存放的a,b,从而进行修改。
三、函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
1、嵌套调用
函数可以嵌套调用,但是不能嵌套定义
2、链式访问
把一个函数的返回值作为另一个函数的参数
举例:
#include <stdio.h>
int main()
{
//printf函数的返回值是打印屏幕上字符的个数
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
运行结果:
这里需要知道的是:printf函数的返回值是打印屏幕上字符的个数
四、函数的声明和定义
1、函数声明
①告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存,函数声明决定不了。
②函数声明一般出现在函数的使用之前。要满足先声明后使用。
③函数的声明一般都要放在头文件中的。
test.h的内容:放置函数的声明
2、函数定义
函数的定义是指函数的具体实现,交代函数的功能实现。
test.c的内容:放置函数的实现
//函数声明
//一般都要放在头文件中
int Add(int x, int y);
int main()
{
int a = 0;
int b = 0;
//输入
scanf("%d %d", &a, &b);
//加法
int c = Add(a, b);//函数调用
//打印
printf("%d\n", c);
return 0;
}
//函数的定义
int Add(int x, int y)
{
return x + y;
}
五、函数递归
1、什么是递归
程序调用自身的编程技巧称为递归(resursion),简单来说就是函数自己调用自己
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归的策略只需少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小。
2、递归的两个必要条件
①存在限制条件,当满足这个限制条件额度时候,递归便不再继续。
②每次递归调用之后越来越接近这个限制条件。
3、递归与迭代
举例:求第n个斐波那契数(不考虑溢出)
//递归的方法
#include <stdio.h>
int feibo(int n)
{
if (n <= 2)
return 1;
else
return feibo(n - 2) + feibo(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = feibo(n);
printf("%d",ret);
return 0;
}
运行结果:
但是我们发现有问题:
使用feibo这个函数的时候如果我们要计算第50个斐波那契数字额时候就会特别耗费时间。
如果我们将代码修改一下:
#include <stdio.h>
int count = 0;//全局变量
int feibo(int n)
{
if(n == 3)
count++;
if (n <= 2)
return 1;
else
return feibo(n - 2) + feibo(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = feibo(n);
printf("%d",ret);
return 0;
}
输出count发现是一个很大很大的值,说明函数在调用的过程中很多计算都在重复。
系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者死递归,这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
那么我们如何改进呢?
1、将递归改为改为非递归。
2、使用 static 对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
采用非递归实现求第n个斐波那契数:
非递归(迭代)的方式
#include <stdio.h>
int feibo(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n>2)
{
a = b;
b = c;
c = a + b;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = feibo(n);
printf("%d",ret);
return 0;
}
运行结果: