下面先给出实例:
int Add(int x, int y)
{
return 0;
}
返回数据类型 函数名 (参数接收)
{
return 返回值;
}
——int为返回数据类型,一般可为char,int,float...常见类型。
——函数名可自行决定(不能为关键字),后可用于函数调用。
——参数接收即为形参设置,接收函数调用时传递的实参数据。
——return 返回值,即函数调用完成后返回的值
需注意,void(即为空类型)类型无返回值
函数能将具有独立功能的代码封装成一个函数,假如在项目中我们需要频繁使用一段代码的功能,只需将其封装成函数,往后每次使用只需通过函数调用进行使用即可。函数封装提高了代码的复用性,并且分模块写代码也提高了代码的可读性,试想一下假如没有函数,写一个程序上万行代码全都在主函数内,调试时那就是折磨了。
函数一旦创建,如果不进行调用,则不会为其分配内存空间。一旦进行调用,才会在栈上开辟空间,创建变量。
函数定义和声明:
函数调用时若函数定义写在函数调用前,则可不进行函数声明。否则若函数定义写在调用之后,则必须在之前进行函数声明。下面为实例:
int add(int x, int y);//函数声明
int main()
{
int x = 10;
int y = 20;
printf("%d", add(x,y));
return 0;
}
int add(int x, int y)
{
return x + y;//函数定义
}
函数声明只需对返回类型,函数名,接收参数部分声明即可,具体需执行代码则放在函数定义内。
从代码中还可看出,函数内形参是可以与外部形参变量同名的。
函数的嵌套调用和链式访问:
嵌套调用,即在一个函数内部调用其他函数
int add(int x, int y)
{
return x + y;
}
void test(int x,int y)
{
printf("%d", add(x, y));//嵌套调用
}
int main()
{
int x = 10;
int y = 20;
test(x, y);
return 0;
}
链式访问,即把一个函数的返回值作为实参传递给另一个函数
int add(int x, int y)
{
return x + y;
}
void test(int x)
{
printf("%d", x);//嵌套调用
}
int main()
{
int x = 10;
int y = 20;
test(add(x,y));//链式访问
return 0;
}
函数的传值调用和传值调用:
int test(int x)
{
x = 1;
return 0;
}
int main()
{
int x = 10;
test(x);
printf("%d", x);
return 0;
}
虽然函数内外都有名为x的变量,但是在函数内部的x为函数形参,在其内对形参x的改变并不会影响实参,因此打印x的原值10。
当然如果x为全局变量函数内部自然可以对其进行修改。
那如何通过函数修改main函数内部变量呢?
此处需要传值调用
int test(int* x)
{
*x = 1;
return 0;
}
int main()
{
int x = 10;
test(&x);
printf("%d", x);
return 0;
}
此处需要引入指针的概念,指针变量可以指向变量所在内存,因此只需传递外部变量地址的值,在函数内部即可通过地址修改外部变量,类似于一个遥控器。指针变量可将函数内部与外部变量联系起来。
函数递归
简单来说就是函数自身调用自身
int main()
{
printf("hello world\n");
main();
return 0;
}
这是最简单的一个递归(虽然并不正确),其中第一个main函数调用自身,然后又进入第二个main(),继续调用自身.......函数递归便形成了"死循环",实际上,执行不就后,代码就会因为"栈溢出"而崩溃。那么为什么这里要形容成第一,第二个main函数呢?不应该是自身调用自身吗?
这样做是为了将其与循环区别开。要搞清楚这些内容,首先要明白函数在栈上存储,因此我们要先介绍执行函数时函数在栈上的情况。
这是函数入栈与出栈,首先执行第1个main函数,直到调用自身,进入第2个main函数,注意!此时第1个main函数并未结束,而栈上又为第2个main函数分配了空间,直到再次调用自身,进入第3个main函数,此时第2个main函数也未结束.......
直到如果函数停止调用自身,开始出栈。假设调用到第6个函数时结束调用,则第6个函数出栈,然后继续往后执行第5个函数,直到第5个函数出栈,然后继续往后执行第4个函数.......
递归是很多算法都使用的一种编程方法,能用极其简洁的代码完成复杂的工作,只需将问题分解为基线条件和递归条件。满足递归条件时,函数调用自身,直到满足基线条件时函数结束调用,开始返回,逐个出栈。 若无基线条件,则函数无限递归,必然导致栈溢出,因此使用递归一般必须有基线条件和递归条件。
现给出用函数递归求n!的代码:
int Fac(int n)
{
if (n == 1)
return 1;
else
return n * Fac(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d",Fac(n));
return 0;
}
函数递归求解问题的思路便是将这个问题转化成更小规模的问题,比如将n!转化成n*(n-1)!,则(n-1)!可继续细分为 (n-1)*(n-2)!,直到基线条件。因此递归返回n*Fac(n-1)即可,直到基线条件时返回1,即可通过不断的自身调用实现阶乘。
函数递归是一种简洁优雅的算法,然而其在运行效率上并不算高效
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d",Fib(n));
return 0;
}
以上为斐波那契数列第n项的递归实现,然而当输入项数较大时,程序便难以得出结果。这是因为此时进行了过多次的重复调用。
可以看出,在求第40项,仅第3项就调用了近4kw次
综上,我们应尽综合考虑代码的简洁与复杂度,函数递归基础知识到此结束