详解C语言函数

本文详细介绍了C语言中的函数概念,包括库函数的使用(如printf和scanf)、自定义函数的语法、形参和实参、return语句、数组作为参数、嵌套调用和链式访问,以及函数的声明和定义、静态变量的运用和函数递归。
摘要由CSDN通过智能技术生成

在这里插入图片描述
👏welcome to my blog
请留下你宝贵的足迹吧(点赞👍评论📝收藏⭐)

函数的概念

 C语言中的函数(function),可理解为子程序。就是⼀个完成某项特定的任务的一小段代码。这段代码是有特殊的写法和调用方法的。
C语⾔的程序其实是由无数个小的函数组合而成的,也可以说:⼀个大的计算任务可以分解成若干个较 小的函数(对应较⼩的任务)完成。同时⼀个函数如果能完成某项特定任务的话,这个函数也是可以 复⽤的,提升了开发软件的效率。
在C语言中我们⼀般会见到两类函数:
• 库函数
• 自定义函数

 1.库函数

1.1标准库和头文件

C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSIC规定了⼀ 些常用的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。
printf 、 scanf 都是库函数,库函数也是函数,不过这些函数是现成的,可以直接使用。
各种编译器的标准库中提供了⼀系列的库函数,这些库函数根据功能的划分,在不同的头文件中进行声明。

1.2库函数的使用方法

库函数的学习和查看工具如:
C/C++官方的链接: https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
举例:sqrt

double sqrt (double x); 
//sqrt 是函数名 
//x 是函数的参数,表⽰调⽤sqrt函数需要传递⼀个double类型的值 
//double 是返回值类型 - 表⽰函数计算的结果是double类型的值 

1.2.1 功能
Computesquareroot计算平⽅根
Returnsthesquarerootofx.(返回平⽅根)
1.2.2 头⽂件包含
库函数是在标准库中对应的头⽂件中声明的,所以库函数的使⽤,务必包含对应的头⽂件,不包含可能会出现⼀些问题。sqrt包含在<math.h>头文件中
1.2.3 使用
在这里插入图片描述

 2.自定义函数

自定义函数根据需要自己设计和实现函数,自己使用

2.1 函数的语法形式

其实自定义函数和库函数的语法形式是⼀样的,形式如下:

ret_type fun_name(形式参数)
 {

 }

• ret_type 是函数返回类型是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表⽰什么都不返回
• fun_name 是函数名 是为了⽅便使⽤函数;就像⼈的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调 ⽤,所以函数名尽量要根据函数的功能起的有意义。
• 括号中放的是形式参数 函数的参数就相当于⼯⼚中送进去的原材料,函数的参数也可以是 void ,表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
• {}括起来的是函数体函数体就是完成计算的过程。

2.2 函数的举例
//写⼀个加法函数,完成2个整型变量的加法操作。
//函数的定义
int Add(int x, int y)//返回的z是int类型的
{
	int z=x + y;
	return z;//将计算结果返回
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	//函数的调用
	int c = Add(a, b);//将a和b的值传给函数,将其计算结果赋给c
	printf("c=%d\n", c);
	return 0;
}
//写一个函数,只需打印“呵呵”就行
void Print()//函数不需要传参(也可以在括号内加上void),也不需要返回值
{
	printf("呵呵\n");
}
int main()
{
	Printf();//调用函数
	return 0;
}

 3.形参和实参

//写一个函数,求两个整数的较大值
int get_max(int a, int b)//函数定义时函数名后括号中的参数是形式参数,简称形参
{
	if (a > b)/*直接return(x>y?x:y)更简单*/
		return a;
	else
		return b;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int m = get_max(a, b);//函数调用时传递给函数的参数是实际参数,简称实参
	printf("%d\n", m);
	return 0;
}

在这里插入图片描述
  实际上,如果只是定义了函数,而不去调用的话,函数的参数只是形式上存在的,不会向内存申请空间,不是真实存在的,所以叫形式参数。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。
结论:
1.实参和形参是不同的内存空间,对形参的修改不会影响实参
2.形参是实参的一份临时拷贝(x,y只是在函数调用时使用了,调用结束就会被回收)

 4.return语句

return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执行表达式,再返回表达式 的结果。
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
• return返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。
• return语句执行后,函数就彻底返回,后边的代码不再执⾏。
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

//写一个函数,判断奇偶
int is_odd(int m)
{
	return m % 2;//表达式
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int a=is_odd(n);
	if (a = 1)
		printf("是奇数");
	else
		printf("是偶数");
	return 0;
}
//写一个函数,依次打印数
void Print(int m)
{
	if (m <= 0)
		return;//如果满足if条件return会直接跳出函数
	else
	{
		int i = 1;
		for (i = 1; i <= m; i++)
			printf("%d ", i);
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	Print(n);
	return 0;
}

int test()
{
	if (1)
		return 3.5;
	else
		return 5.5;
}
int main()
{
	int r = test();
	printf("%d\n", r);//打印出3
	return 0;
}

 5. 数组做函数参数

数组传参的几个重点知识:
• 函数的形式参数要和函数的实参个数匹配
• 函数的实参是数组,形参也是可以写成数组形式的
• 形参如果是⼀维数组,数组大小可以省略不写
• 形参如果是⼆维数组,行可以省略,但是列不能省略
• 数组传参,形参是不会创建新的数组的
• 形参操作的数组和实参的数组是同⼀个数组
在这里插入图片描述

在这里插入图片描述

 6.嵌套调⽤和链式访问

6.1 嵌套调用

嵌套调用就是函数之间的互相调⽤,每个函数就像⼀个乐⾼零件,正是因为多个乐高零件互相无缝地配合才能搭建出精美的乐高玩具,也正是因为函数之间有效的互相调用,最后写出来了相对大型的 程序。
函数中不可再定义一个其他的函数,但函数可以嵌套调用

//计算某年某月有多少天
//闰年的判断:1.能被4整除,并且不能被100整除;2.能被400整除
int is_leap_year(int y)
{
	if (((y %4 == 0) && ((y %100) != 0) )|| (y %400 == 0))
	return 1;
	else
		return 0;
}
int get_days_of_month(int y, int m)
{
	int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
//对应数组下标依次为0,1,2,3,4,5,6,7,8,9,10,11,12把天数与月份对应起来了
	/*也可以使用switch语句
	* int day=0;
	* switch(m)
	* {
	* case 2:
	*      day=28;
	*      break;
	* case 1:
	* case 3:
	* case 5:
	* case 7:
	* case 8:
	* case 10:
	* case 12:
	*      day=31;
	*      break;
	* case 4:
	* case 6:
	* case 9:
	* case 11:
	*      day=30;
	*      break;
	*/
	int day = days[m];
	if (is_leap_year(y) && m == 2)//嵌套调用
		day += 1;
	return day;
}

int main()
{
	int year = 0;
	int month = 0;
	scanf("%d %d", &year, &month);
	int d=get_days_of_month(year, month);
	printf("%d\n", d);
	return 0;
}
6.2 链式访问

链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。

int main()
{
	printf("%zd\n", strlen("abc"));//链式访问
	return 0;
}

在这里插入图片描述

 7.函数的声明和定义

7.1 单个⽂件
//写⼀个函数判断⼀年是否是闰年
//程序从前往后执行,若自定义函数是在main函数之后,则应提前声明自定义函数
int is_leap_year(int y);//函数的声明,声明函数名,参数,返回类型
int main()
{
	int x = 0;
	scanf("%d", &x);
	int r = is_leap_year(x);
	if (r == 1)
		printf("Yes\n");
	else
		printf("No\n");
	return 0;
}
//函数的定义
int is_leap_year(int y)
{
	if (((y % 4 == 0) && ((y % 100) != 0)) || (y % 400 == 0))
		return 1;
	else
		return 0;
}
7.2 多个⽂件

c – 源文件
存放函数的定义
.h – 头文件
存放函数的声明,类型的声明
在这里插入图片描述

7.3 static和extern

static 和extern 都是C语⾔中的关键字。
static是静态的的意思,可以⽤来:
• 修饰局部变量
• 修饰全局变量
• 修饰函数
extern 是用来声明外部符号的。
补充📚:作用域(scope)是程序设计概念,通常来说,⼀段程序代码中所而到的名字并不总是有效(可用)
的,而限定这个名字的可而性的代码范围就是这个名字的作用域。

  1. 局部变量的作用域是变量所在的局部范围。
  2. 全局变量的作用域是整个工程(项目)。
    生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。
  3. 局部变量的生命周期是:进⼊作用域变量创建,生命周期开始,出作用域生命周期结束。
  4. 全局变量的生命周期是:整个程序的生命周期。
7.3.1 static修饰局部变量:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结论:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本 来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变 量和全局变量是⼀样的,生命周期就和程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作用域不变的。
使用建议:未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使用,就可以使用static修饰。

7.3.2 static修饰全局变量

在这里插入图片描述
源文件编译时是单独编译的
teat1.c中的全局变量a是具有外部链接属性的,使用extern(用来声明外部符号)可使其在test.c文件中被打印,也可被重新赋值使用
若teat1.c中的a被static修饰后外部链接属性就变成了内部链接属性,a就只能在test1.c中使用,使用extern后也不可在test.c文件中使用(这里就不再放图了,可自行测试)
使用建议:如果⼀个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰。

7.3.3 static修饰函数

在这里插入图片描述
与全局变量一样函数默认也是具有外部链接属性,但是被static修饰后变成了内部链接属性,使得函数只能在自己所在源⽂件内部使用。其他源⽂件即使声明了,也是无法正常使用的。(这里就不再放图了,可自行测试)
使用建议:⼀个函数只想在所在的源文件内部使用,不想被其他源文件使用,就可以使用static修饰。

 8.函数递归

思想:
把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较小的子问题来求解;直到子问题不能再 被拆分,递归就结束了。所以递归的思考方式就是把大事化的小过程。
递归中的递就是递推的意思,归就是回归的意思,
限制条件:
递归在书写的时候,有2个必要条件:
• 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
• 每次递归调用之后越来越接近这个限制条件。
注意👀

void test(int n)
{
	printf("%d ", n);
	if (n < 10000)//存在限制,并且每次递归都更接近限制条件
		//但由于10000太大,递归层次太深,导致栈溢出
		test(n + 1);
}
int main()
{
	test(1);
	return 0;
}
/*举例1:求n的阶乘
⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
⾃然数n的阶乘写作n!(n!=n*(n-1)!)
题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。*/
int Fact(int n)
{
	if (n == 0)
		return 1;
	else
		return  n * Fact(n - 1);//函数的递归
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int r = Fact(n);
	printf("%d\n", r);
	return 0;
}

图解
在这里插入图片描述
在这里插入图片描述
图解
在这里插入图片描述

8.1递归和迭代

斐波那契数
在这里插入图片描述

/*求第n个斐波那契数*/
int count = 0;
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);
	int r = Fib(n);
	printf("%d\n", r);
	printf("count=%d\n", count);
	return 0;
}

当我们输⼊n为50的时候,需要很长时间才能算出结果,这说明递归的写法是非常低效的,但如果我们使用迭代的方式(通常就是循环的⽅式),就会高效很多

int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;//实现了将两个数相加的和作为下次相加的因子
		n--;
	}
	return c;
}
int main()
{
	int n=0;
	scanf("%d", &n);
	int d = Fib(n);
	printf("%d\n", d);
	return 0;
}

💓期待你的一键三连,你的鼓励是我创作的动力之源💓
在这里插入图片描述

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

engrave行而不辍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值