首页 > 其他分享 >C语言中的函数(2)

C语言中的函数(2)

时间:2024-05-25 17:30:47浏览次数:11  
标签:return 函数 int C语言 static printf 全局变量

目录

前言

函数的调用和声明

函数的嵌套调用 

函数的链式访问

函数的递归调用

递归求n的阶乘

递归计算斐波那契数

static和extern

作用域和生命周期

变量存储方式

作用

static修饰局部变量

extern的使用

static修饰全局变量

static修饰函数

函数的要求

       内聚性强

        耦合性弱



前言

    在前面,我们学习一些函数的一些基础知识。接下来我们进一步的来深入理解函数并且进行一些应用。

函数的调用和声明

      前面我们提到过自定义函数,在一个函数中我们可以使用函数名(实参)来调用另外一个函数,   函数的调⽤⼀定要满⾜,先声明后使⽤,告诉系统需要调用函数的位置才可以使用函数,如果函数的定义在函数调用的后面我们就需要在这个函数的前面进行声明。

声明的形式也就是定义的函数的首行再加上一个分号。

如果函数的定义在函数调用的前面,我们可以不进行声明,我们可以理解为函数的定义是⼀种特殊的声明。

例:写一个函数判断闰年。

#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

相关文章

  • 数据库函数下拉式求和
    问题:如何用Dsum实现单条件求和的下拉函数解决:=DSUM($C$1:$E$9,D$1,$K$1:$K2)-SUM(L$1:L1)Dsum公式在第2行实现的是股票名称为A的求和结果;到第3行时变成股票名称为A和B的求和结果,这时需要减掉上一个单元格的数据;到第4行则需要减掉上两个单元格求和的数据。使用Sum(L$1:L1)......
  • 探索c语言:深入了解指针
    1.内存和地址1.1内存和地址1.1内存我们可以通过一个小案例来了了解:假设有一栋宿舍楼,把你放在楼里,楼上有100个房间号,但房间里没有编号,刚好你的一个朋友找你玩,如果想要找到你就得挨个房间找,这样子效率很低,但是如果我们根据楼层和楼层的房间号的情况,给每个房间编上号,如: 1......
  • 构造函数
    类成员初始化方式:1、通过构造函数的参数列表初始化。2、在构造函数中赋值完成初始化。//1、通过构造和函数的参数列表初始化Seles_data::Sales_data(constSales_data&sa){ this->bookNo=sa.bookNo; this->revenue=sa.revenue; this->units_sold=sa.units_sold;}......
  • 关于字符串的功能函数小结
    笔者做项目过程中,使用了很多关于字符串的C库中自带功能函数,极大便利了项目流程。再次做一个小结,之后若有增加会继续补充。所需采用函数序号函数/描述1char*strcat(char*dest,constchar*src)【char*strncat(char*dest,constchar*src,size_tn)】功能:连接......
  • C语言---最大公约数和最小公倍数的求法
    #include<stdio.h>//欧几里得算法求的最大公约数intgcd(inta,intb){//一定要确保a>bif(a<b){inttemp=a;a=b;b=temp;//作用是创建临时变量将a和b的数值置换}while(b!=0)//当b不等于0时,继续执行循环......
  • C语言---数组中逆序输出--新
    #include<stdio.h>intmain(){//下面的是输入intarr[10]={0};//创建一个大小为10的数组for(inti=0;i<10;i++){scanf("%d",&arr[i]);//循环输入i的值}//为什么是i从9开始,不是从0开始//因为总共10个数,所以最大数......
  • 【C++】C++异常处理精要:从传统C语言错误处理到现代C++异常机制
    文章目录前言:1.C语言传统的处理错误的方式2.什么是异常处理机制?3.C++异常处理语法3.1.异常抛出(Throw)3.2.异常捕获(Catch)3.3.异常传递(ExceptionPropagation)3.4.异常规范(ExceptionSpecification)3.5.异常安全(Exceptionsafe)4.C++异常处理的最佳实践4.1.只在必......
  • C语言开发流程与编译四部曲
    1、编写代码(1)文件格式要求源代码:.c头文件.h(2)编写过程要求使用英文字符(3)中英文切换需要注意全半角问题(4)字符编码问题(Linux:UTF-8)error:stray'\342'inprogram以上错误为中文及圆角问题2、生成程序(1)编译型语言:c/c++(2)解释型语言:py(3)若没有编译器(gcc)sudoaptinstall......
  • 吴恩达机器学习 week1 一元回归模型的成本函数
    01学习目标    学习建立一元线性回归模型的成本函数02实现工具  (1)代码运行环境       Python语言,Jupyternotebook平台  (2)所需模块       NumPy,Matplotlib,lab_utils_uni      (lab_utils_uni是课程中用于绘制复......
  • 【C语言】文件的编译链接和预处理
    文件的编译链接和预处理程序的翻译环境和执行环境翻译环境预处理(预编译)过程编译过程汇编过程链接过程运行环境预处理详解预处理符号预处理指令#define#define定义标识符#define定义宏#define替换规则#与###的使用##的使用带有副作用的宏参数宏与函数的对比宏的优势函......