目录
前言
在前面,我们学习一些函数的一些基础知识。接下来我们进一步的来深入理解函数并且进行一些应用。
函数的调用和声明
前面我们提到过自定义函数,在一个函数中我们可以使用函数名(实参)来调用另外一个函数, 函数的调⽤⼀定要满⾜,先声明后使⽤,告诉系统需要调用函数的位置才可以使用函数,如果函数的定义在函数调用的后面我们就需要在这个函数的前面进行声明。
声明的形式也就是定义的函数的首行再加上一个分号。
如果函数的定义在函数调用的前面,我们可以不进行声明,我们可以理解为函数的定义是⼀种特殊的声明。
例:写一个函数判断闰年。
#include<stdio.h>
int main()
{
//函数的定义在函数调用的后面需要进行函数声明
int is_leap_year(int y);//函数定义首行+分号
int year = 0;
printf("请输入年份:\n");
scanf("%d", &year);
if (is_leap_year(year))//函数调用
printf("%d是闰年\n", year);
else
printf("%d不是闰年\n", year);
}
int is_leap_year(int y)//函数定义
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
return 1;
else
return 0;
}
#include<stdio.h>
int is_leap_year(int y)//函数定义
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
return 1;
else
return 0;
}
int main()
{
//函数的定义在函数调用的前面可以不进行函数声明
int year = 0;
printf("请输入年份:\n");
scanf("%d", &year);
if (is_leap_year(year))//函数调用
printf("%d是闰年\n", year);
else
printf("%d不是闰年\n", year);
}
函数的嵌套调用
嵌套调⽤就是函数之间的互相调⽤,我们可以在一个函数中调用另外一个函数,就向前面的代码中我们在main函数中调用其他的函数来进行操作。需要特别注意的是,函数之间可以嵌套调⽤,但是函数是不能嵌套定义。
接下来,我们用一个简单的例子来进行更好的理解
例:找四个数的最小值
#include<stdio.h>
int max4(int a, int b, int c, int d)
{
int max2(int m, int n);//函数声明
int max = a;//最大值最开始假设为a
max = max2(max, b);//把a,b的较大者赋给max
max = max2(max, c);
max = max2(max, d);
return max;
}
int max2(int m, int n)
{
return m > n ? m : n;//条件表达式
}
int main()
{
int a = 0, b = 0, c = 0, d = 0;
printf("请输入四个整数:");
scanf("%d %d %d %d", &a, &b, &c, &d);
int ret = max4(a, b, c, d);
printf("最大值为%d\n", ret);
return 0;
}
我们可以看到,在main函数中我们调用了max4函数,又在max4函数中调用了max2函数。不同的函数完成了不同的功能。
函数的链式访问
链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数 的链式访问。
#include<stdio.h>
int max2(int m, int n)
{
return m > n ? m : n;
}
int main()
{
int a = 0;
int b = 0;
printf("请输入两个数a,b\n");
scanf("%d %d", &a, &b);
int ret = max2(a, b);
printf("最大值为:%d\n", ret);
return 0;
}
#include<stdio.h>
int max2(int m, int n)
{
return m > n ? m : n;
}
int main()
{
int a = 0;
int b = 0;
printf("请输入两个数a,b\n");
scanf("%d %d", &a, &b);
printf("最大值为:%d\n", max2(a, b));
return 0;
}
上面这两个代码有什么区别呢?
区别就是第一段代码使用了ret来接收max2函数的返回值,再使用printf函数进行打印;而第二段代码,直接将把如果把max2的返回值直接作为printf函数的参数,没有创建第三个变量,这就是函数的链式访问。
趣味代码
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
这一段代码运行的结果是什么呢?
我们可以看到运行结果为4321。
为什么是这样的运行结果呢?我们就需要了解printf函数了。
在前面我们提到了两个网站,我们可以选择一个来进行搜索,
我们可以知道printf函数返回的是 打印在屏幕上的字符个数 。 我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个printf的返回值。 第三个printf打印43,在屏幕上打印2个字符,返回值为2 第⼆个printf打印2,在屏幕上打印1个字符,返回值为1 第⼀个printf打印1,所以结果为4321.函数的递归调用
在调用一个函数的过程中又直接或者间接地调用函数该函数本身,就是函数的递归调用。简单来说,就是函数⾃⼰调⽤⾃⼰。递归的思考⽅式就是把⼤事化⼩的过程。
递归在书写的时候,有2个 必要条件 : • 递归存在 限制条件 ,当满⾜这个限制条件的时候,递归便不再继续。 • 每次递归调⽤之后 越来越接近这个限制条件 我们通过下面的例子来进行更好地理解:递归求n的阶乘
首先我们进行一个简单的分析:
n!=n*(n-1)!
(n-1)!=(n-1)(n-2)!
...........
当n的值等于0或者1的时候,结果就是1,所以这个递归的限制条件就是n等于0或者1.
#include<stdio.h>
int dn(int n)
{
if (n == 0 || n == 1)
return 1;
else
return n * dn(n - 1);//调用函数自己,求(n-1)的阶乘
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d!= %d", n, dn(n));
return 0;
}
我们通过画图来更好的理解
递归计算斐波那契数
斐波那契数:1 1 2 3 5 8 13 21 34 55 89.......
我们可以看出斐波那契数的规律是从第三项开始为前两项之和,它的限制条件就是n等于1或者2的时候结果为1.
#include<stdio.h>
int fac(int n)
{
if (n == 1 || n == 2)
return 1;
else
return fac(n - 1) + fac(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("第%d个斐波拉契数是%d",n, fac(n));
return 0;
}
当输入10的时候,很快就会出现结果,如果输入50,光标一直在闪烁,但是很久没有输出结果,这是因为递归程序不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计 算,⽽且递归层次越深,冗余计算也就会越多。所以我们就需要合理地使用递归,不要迷恋递归。
我们可以非递归的方式来计算斐波拉契数
#include<stdio.h>
int fac(int n)
{
int f1 = 1;
int f2 = 1;
int f3 = 0;
for (int i = 0; i < n - 2; i++)
{
f3 = f1 + f2;
f1 = f2;
f2 = f3;
}
return f3;
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("第%d个斐波拉契数是%d",n, fac(n));
return 0;
}
这样效率也就更高!
static和extern
正式开始之前,我们需要先了解一些知识。
作用域和生命周期
作⽤域(scope)是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是有效(可⽤)的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。 1. 局部变量 的作⽤域是 变量所在的局部范围 。 2. 全局变量 的作⽤域是 整个⼯程(项⽬) 。 ⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。 1. 局部变量的⽣命周期是: 进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束 。 2. 全局变量的⽣命周期是: 整个程序的⽣命周期 。 局部变量和全局变量在前面 C语言基础 中讲过,这里就不再进行更多的描述,可以简单理解为局部变量是在{ }内部定义的变量,而全局变量是在{ }外部定义的变量。变量存储方式
内存中的三个区域:栈区、堆区、静态区。
它们存放的内容分别是:
栈区:局部变量,函数参数
静态区:全局变量,静态变量(static修饰)
堆区:动态内存管理
作用
我们来简单介绍一下这两个关键字。
static 是 静态的 的意思,可以⽤来: • 修饰局部变量 • 修饰全局变量 • 修饰函数 extern 是⽤来声明外部符号的。static修饰局部变量
我们先来看看这两段代码
代码1
#include<stdio.h>
int f(int a)
{
int b = 0;//局部变量
b++;
return a + b;
}
int main()
{
int a = 3;
int i = 0;
for (i = 0; i < 3; i++)
{
printf("f(a)=%d\n", f(a));
}
return 0;
}
代码2
#include<stdio.h>
int f(int a)
{
static int b = 0;//static修饰局部变量
b++;
return a + b;
}
int main()
{
int a = 3;
int i = 0;
for (i = 0; i < 3; i++)
{
printf("f(a)=%d\n", f(a));
}
return 0;
}
我们发现使用static修饰b之后,它的结果发生了变化。
代码1的test函数中的局部变量b是每次进⼊f函数先创建变量(⽣命周期开始)并赋值为0,然后 ++,再打印,出函数的时候变量⽣命周期将要结束(释放内存)。 代码2中,我们从输出结果来看,b的值有累加的效果,其实 f 函数中的b创建好后,出函数的时候是不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算。 我们可以理解为, 对静态局部变量只会赋初值一次,后面每次调用函数不再重新赋初值, 下一次使用是上一次函数调用结束时的值。 static修饰局部变量 改变了变量的⽣命周期 ,⽣命周期改变的本 质是 改变了变量的存储类型 ,本 来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变 量和全局变量是⼀样的,⽣命周期就和程序的⽣命周期⼀样了, 只有程序结束,变量才销毁 ,内存才回收,但是 作⽤域没有发生改变 。extern的使用
extern 是⽤来声明外部符号的,如果⼀个全局的符号在A⽂件中定义的,在B⽂件中想使⽤,就可以使⽤ extern 进⾏声明,然后使⽤。static修饰全局变量
前面的代码如果我们在用static修饰全局变量a,那么编译器就会报错。
所以⼀个 全局变量被static修饰 ,使得这个 全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤ 。 本质是因为 改变了链接属性 ,全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后, 外部链接属性就变成了内部链接属性 ,只能在⾃⼰所在的源 ⽂件内部使⽤,其他源⽂件即使声明了也是⽆法正常使⽤。static修饰函数
代码1
代码2
我们发现和static修饰全局变量出现了一样的错误。 事实上,static 修饰函数和 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程都可以使⽤, 被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了 。 本质是因为函数也默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部 使⽤。 使⽤建议:⼀个函数或者一个全局变量只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修 饰。函数的要求
内聚性强
函数的功能尽量单一(一个函数不要混合太多的功能)
耦合性弱
与其他函数的相互影响尽量少
所以我们一般不推荐过多地使用全局变量,降低了程序的可靠性和通用性,同时会降低程序的清晰性。
标签:return,函数,int,C语言,static,printf,全局变量 From: https://blog.csdn.net/2401_82924480/article/details/139042971