目录
1.函数定义
2.函数类型
2.1自定义函数
2.2库函数
3.实参与形参
4.函数的调用
5.函数的声明
6.函数的嵌套调用和链式访问
6.1嵌套调用
6.2链式访问
7.函数递归
博主水平有限,文章中若有错误,望读者们斧正。
维基百科中,函数的定义是
函数在c语言中是可以自定义的,那如何定义一个函数呢?
void fun_name(int x)
{
函数语句;
}
1)void是函数返回类型(如果函数在运行完成后需要给主程序返回一个值,我们就要用相应的类型去定义这个函数。要返回整形,就用 int去定义函数,不需要返回我们就用void类型)
2)fun_name是函数的名字,名字要起的与函数功能匹配
3)括号里的int是参数类型,x是函数参数(参数可以有一个或多个,但参数要尽量少),当然也可以没有参数,让程序直接运行函数的语句
void test()
{
函数语句;
}
4)如果函数有返回值,调用函数时我们要创建一个变量接收返回值。没有变量时,我们直接调用就行
//1有返回值时
int fun(int x)
{
语句;
}
int main()
{
int a = 0;
int ret = fun(a);
return 0;
}
//2没有返回值时
void fun(int x)
{
语句;
}
int main()
{
int a = 0;
fun(a);
return 0;
}
我们举一个例子:自定义一个函数,找出两个数中的最大值。那我们接下来就要创建一个有两个参数的函数,并且函数将会返回两个数中的最大值,返回值是整形,所以我们用 int类型创建一个名为Max的函数。要怎么调用我们创建的函数呢?我们只要写上函数名,再将要求最大值的两个数以逗号间隔,放进括号中
Max(a, b)
//找出两个数中的最大值
int Max(int x, int y)//设定两个函数参数
{
return (x > y ? x : y);
}
int main()
{
int a = 10;
int b = 20;
//当程序运行到这时,函数会被程序调用
//函数返回最大值,我们创建 max 变量接收最大值
int max = Max(a, b);
printf("%d", max);
return 0;
}
这样Max 函数就写好了。
cplusplus.com - The C++ Resources Network
(函数返回类型与应用举例)
函数在不同情况下会返回不同的值,在Return Vaule中都具体介绍了。如果看完后还是不懂要怎么使用这个库函数,我们还能通过Example,看看别人是怎么使用这个函数的。
最后我们在使用库函数时一定要引用头文件。可以看到,printf的头文件是熟悉的stdio.h,我们就要在程序开始处加上#include
在函数中 x,y被称作形式参数(形参),a,b则是实际参数(实参)
结合刚刚的Max函数理解形参与实参
//找出两个数中最大值
int Max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 10;
int b = 20;
int c = Max(a, b);
printf("%d", c);
return 0;
}
它们有什么区别?形参其实是实参的一份临时拷贝。换言之,形参不等于实参,在函数运行时,程序为形参单独开辟内层空间,在程序结束时,程序销毁形参。两者的内存空间不同,所以改变形参的值不能改变实参。
我们再来一个例子
void Swap(int x, int y)
{
int z = x;//两数交换时创建第三个变量,就像交换酱油与醋,我们要找第三个瓶子
x = y;
y = z;
}
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);
}
这是一个交换函数,用它我们交换a与b的值,那结果是什么?
两个数没有发生交换,为什么?这是因为形参是实参的临时拷贝,改变形参的值不能改变实参的值。要实现两数交换,我们可以把地址传给形参,对地址解引用,找到实参,从而间接的改变实参。
把地址作为实参传给形参的调用函数方式称为传址调用,把实参的值作为实参传给形参的方式叫做传值调用。
来看看交换函数的正确写法
void Swap(int* x, int* y)
{
int z = *x;
*x = *y;
*y = z;
}
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);
}
运行结果
所以在写函数时要先考虑调用方式是传值调用还是传值调用。要想通过函数改变外部变量的值,我们通常传址调用,反之我们就使用传值调用。
如果我们在调用函数时,将函数写到了主函数的后面,程序会报一个警告,因为程序在运行时总是从上到下运行的,程序要调用一个函数,但是它却不认识这个函数,更不要说去调用它了。所以我们在调用函数时要满足先声明后使用,声明就是告诉程序这个函数叫什么,返回类型是什么,参数有什么...而声明一般会放到头文件中。(这里先简单介绍下,之后我会接着补充怎么具体声明)
#include
//这就是声明Swap函数的格式
void Swap(int* x, int* y);
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);
}
//把Swap函数放在主函数的后面
void Swap(int* x, int* y)
{
int z = *x;
*x = *y;
*y = z;
}
什么是嵌套调用?在函数中调用其他函数的方式被称为嵌套调用。
void hehe()
{
printf("hehe");
}
void three_hehe()
{
hehe();
hehe();
hehe();
}
这里举一个简单的例子。hehe函数的作用是打印 “hehe”,three_hehe函数的作用是打印三个 “hehe” ,函数中一共调用了三次hehe函数。three_hehe中调用了 hehe函数。这样就是嵌套调用了。
那什么又是链式访问呢?
将一个函数的返回值作为另一个函数的参数。我们再举一个例子
//strlen:求字符串长度,返回值为字符串中字符个数
//方式1
int main()
{
char a[] = "abcde";
printf("%d", strlen(a));
return 0;
}
//方式2
int main()
{
char a[] = "abcde";
int ret = 0;
ret = strlen(a);
printf("%d", ret);
return 0;
}
两种方式哪种是链式访问呢?答案是方式1。方式1将 strlen函数的运算结果直接作为printf函数的参数。方式2中,我们通过创建中间变量 ret存储strlen函数的返回值,最后打印 ret的值,是一种间接使用 strlen函数返回值的方式。两种方式各有优缺点,方式 1简洁,代码量少,方式 2虽然代码多,但是对于新手来说可读性高。
前面我们介绍了函数的嵌套调用(在函数中调用其他函数),递归呢,就是函数自己对自己的嵌套调用,自己调用自己。运用递归我们能将大事化小,用少量代码解决复杂问题。但是函数如果不停地调用自己,我们的程序是会崩溃的,所以我们要有一个终止条件,当函数满足该条件时,停止调用自己,并且我们在每一次调用函数后,更接近这个条件。
先来看一个简单的递归:如何递归来求 n 的阶乘?
分析:(假设函数名为fac)当 n为1时,函数直接返回1。n大于1时,函数返回 n * fac(n - 1),直到fac(n - 1)中的(n - 1)为1,函数停止递归。
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;
}
假设我们要求5 的阶乘,画图理解。
红色代表的是函数”递“的部分,程序不断的调用fac函数,一层层往下接近终止条件,绿色代表的是函数”归“的部分,当程序遇到终止条件,会 返回函数值,一层层向上地返回函数值,直到递归的起点。
我们还能用递归求第 n个斐波那契数。(斐波那契数列:1,1,2,3,5,8,13,21,34,55...从第 3项开始每一项都是前两项的和)
可以这样分析,当 n 小于等于 2时,斐波那契数为1,当 n大于 2时,f(n) = f(n - 1)+ f(n - 2)。当 n大于 2我们就进行递归,每次递归调用函数时 n都减一,直到(n - 2) 与 (n -1)这两个表达式满足终止条件,程序结束'递',开始往上'归'。
int Fib(int x)
{
if (x <= 2)
{
return 1;
}
else
{
return Fib(x - 1) + Fib(x - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d", Fib(n));
return 0;
}
最后,由于前几天状态不佳,总结这篇博客时相当敷衍,在进行了删改之后重新发布这篇博客。希望以后能及时调整自己,保持一个学习的状态。