【c语言初阶】函数与递归知识总结


铁汁们~今天给大家分享一篇函数与递归的详解,来吧,开造⛳️

函数定义与分类

函数的定义:

1.函数,又被其他地区成为子程序,是一个大型工程项目的部分代码,由一个或多个语句块【】组成,负责完成某项 特定的任务,相对于其他代码,具有相对的 独立性

2.C语言中把常用的功能进行了封装,封装成一个个函数,提供出来大家都可以进行使用—>库函数:如输入输出型库函数scanf、printf,字符串拷贝函数strcpy,字符串求长度函数strlen等。

3.c语言并不是直接去实现库函数的,而是提供了c语言标准以及库函数约定,c语言标准为:函数功能、函数参数、返回值类型、函数名,库函数的实现一般是由编译器去实现的,实现库函数要遵循c语言标准以及库函数约定,相当于连锁商店。

注意:使用库函数之前一定要引用其相对应的头文件,否则编译器识别不出来,编译器会报错。

库函数

库函数的定义与种类

1.库函数的定义:大家都要频繁去使用的功能性函数。

2.库函数的种类:
a.输入输出库函数(引用的头文件为#include<stdio.h>):prinf(打印)、scanf(输入)。
在这里插入图片描述

b.字符串库函数(引用的头文件为#include<string.h>):字符串的拷贝strcpy库函数、字符串求长度strlen库函数、字符串比较strcmp库函数。
在这里插入图片描述

c.数学库函数(引用的头文件为#include<math.h>):求n的k次方pow库函数、求n开根号sqrt库函数等。
在这里插入图片描述

二级

查询库函数性质工具的使用

工具1:MSDN
使用步骤:输入要查询的函数—>索引
在这里插入图片描述
2www.cplusplus.com网站

第一步:
在这里插入图片描述
第二步:
在这里插入图片描述
第三步:
在这里插入图片描述

c语言中常用的库函数总结

IO函数:输入输出函数:<stdio.h>,printf、scanf、putchar。

字符串操作函数:<string.h>,strlen、strcpy、strcmp等。

内存操作函数:<string.h>,memset、memcmp等。

时间或日期操作函数:<time.h>,time等。

数学函数:<math.h>,sqrt、pow等。

其他库函数:产生随机数函数:rand、srand、time; islower、isupper、tolower、toupper、isalpha等<ctype.h>。

自定义函数

自定义函数与库函数相同:有函数名、函数参数、返回值类型。
函数的组成:
在这里插入图片描述
在这里插入图片描述

自定义一个函数找出两个整数的最大值

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int SearchMax(int a, int b)
{
	return a > b ? a : b;
}
int main()
{
	int a = 0;
	int b = 0;
	int max = 0;
	scanf("%d %d", &a, &b);
	max=SearchMax(a, b);
	printf("最大值为=%d", max);
	return;
}

在这里插入图片描述

函数的参数

实参定义

实际参数:又称实参,是指函数调用时函数名后面的括号,实参的种类可以是常量、变量、表达式、函数等,无论参数为何种类型,在函数的调用时,实参必须为确定的值,,以便于将该值很好的传递给形参。

形参定义

形式参数:又称为形参,是函数调用操作符里面的内容,形式参数只有在函数被调用时才被实例化(分配内存单元),在栈上开辟空间,当函数调用完后就自动销毁了,形式参数只在函数体中有效。

函数调用

写一个函数可以交换两个整形变量的内容

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void swap1(int a, int b)  //传值调用
{                         //a、b为形参
	int tmp = a;
	a = b;
	b = tmp;
}
void swap2(int* a, int* b)//传址调用
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前a=%d,b=%d", a, b);
	swap1(a, b);                       //a、b为实参
	printf("swap1交换后a=%d,b=%d", a, b);
	swap2(&a, &b);
	printf("swap2交换后a=%d,b=%d", a, b);
	return 0;
}

在这里插入图片描述

传值调用

定义:函数的形参和实参分别占有不同的内存块,对形参的改变不会影响实参的改变,形参只是实参的一份临时拷贝

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void swap1(int a1,int b1)//传值调用
{                         //a、b为形参
	int tmp = a1;
	a1 = b1;
	b1= tmp;
}
int main()

{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前a=%d,b=%d\n",a,b);
	swap1(a, b);                       //a、b为实参
	printf("swap1交换后a=%d,b=%d\n", a, b);
	return 0;
}

在这里插入图片描述

tips:运行错误:该代码可以通过编译器编译通过,无语法错误,但不能达到我们预期的效果。

未交换的原因:

形参只是实参的一份临时拷贝,形参的改变不会影响实参的改变 ,因为形参所占空间的地址与实参所占空间的地址不同,属于两个不同的独立空间,实参只是把值传递给了形参,形参的变化不会影响实参的变化,两者之间未建立联系

图解分析

在这里插入图片描述

传址调用

定义:把在函数外部创建的变量地址传递给函数的形参的一种调用函数的方式。

意义:可以将函数形参与函数外部变量实参建立起真正的联系,函数的内部可以直接操作函数外部的变量,即:通过指针就可以访问外部变量,对其直接进行操作,形参的改变会引起实参的变化。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void swap2(int* a1, int* b1)//传址调用
{
	int tmp = *a1;
	*a1 = *b1;
	*b1 = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前a=%d,b=%d", a, b);
    swap2(&a, &b);
	printf("swap2交换后a=%d,b=%d", a, b);
	return 0;
}

交换的原因:

因为指针变量可以与它所指向的变量建立起联系,指针变量可以修改它指向对象的值,a1中存放着a中的地址,可以通过存放的地址找到a,b1中存放者b中的地址,可以通过存放的地址找到b,a1与b1的交换实际上是函数体外a与b的交换,形参的改变会引起实参的改变。
在这里插入图片描述

图解分析

在这里插入图片描述

编程题

判断一个数是不是素数

思路:素数为只能被1和自己本身整除的数,不能被2到本身-1之间的数整除,素数不能被2到sqrt(n)之间的数整除。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int issushu(int n)
{
	int i = 0;
	for (i = 2; i <= sqrt(n); i++)
	{
		if (n % i == 0)
			return 0;
	}
	return 1;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret=issushu(n);
	if (ret == 1)
	{
		printf("%d是素数\n", n);
	}
	else
	{
		printf("%d不是素数\n", n);
	}
}

在这里插入图片描述

判断一年是不是闰年

思路:闰年能被4整除但不能被100整除或者闰年能被400整除。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int runyear(int year)
{
	return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int main()
{
	int year = 0;
	scanf("%d", &year);
	int ret=runyear(year);
	if (ret == 1)
	{
		printf("%d是闰年\n", year);
	}
	else
	{
		printf("%d不是闰年\n", year);
	}
}

在这里插入图片描述

实现一个整形有序数组的二分查找。

思路及代码实现在博主的另一篇博文有详解,请点击链接二分查找编程题

函数嵌套调用和链式访问

函数和函数之间可以根据实际的需求进行组合,也就是函数互相调用

嵌套调用

定义:一个函数里面调用了一个或多个函数,该函数可以是该函数本身,也可以是其他函数。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void print()
{
    printf("happy\n");
}
void test()  //函数定义
{
	print();  //函数嵌套调用
}
int main()
{
	test();  //函数调用
	return 0;
}

在这里插入图片描述

注意:函数可以嵌套调用,但不可以嵌套定义

嵌套定义

概念:一个函数定义中嵌套了另一函数定义,编译器不允许此操作。

#define _CRT_SECURE_NO_WARNINGS 1  //错误代码
#include<stdio.h>
void test()
{
	void print()
	{
		printf("happy\n");
	}
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述

链式访问

定义:把一个函数的返回值作为另外一个函数的参数。

链式访问经典题目

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	printf("%d", printf("%d ", printf("%d ", 43)));
	return 0;
}

在这里插入图片描述

解释:printf函数的返回值为在屏幕上打印的字符总个数,包括空格
先执行该函数本身的功能,在将该函数的返回值作为另一个函数的参数,从后往前看,第一个printf在屏幕上打印出 43空格,返回值为3,第二个printf在屏幕上打印 3空格,返回值为2,第三个printf在屏幕上打印2。

函数的声明和定义

函数的声明

函数声明的作用:

1.先提前告诉编译器该函数的相关信息,如:函数名、函数参数、函数返回值类型,参数名可省略,eg:int add(int,int)。但是在代码中该函数存不存在,函数的声明决定不了。
2.函数声明一般放在函数定义之前。必须要满足先声明后定义。、
3.函数的声明一般放在头文件中。

函数定义如果放在函数调用前面,则函数的定义是一种特殊的函数声明,可省略函数的声明。

函数定义如果放在函数调用后面,则必须要满足先声明后定义。

函数的定义

函数的定义:是指函数的具体实现,交代函数功能的实现。

函数声明和定义的实际应用

eg1:当程序员进入企业时,完成一个大工程,需要多个程序员分模块编写代码,方便协作,最后做整合。
在这里插入图片描述
eg2:程序员在业余时间写了一个库,完成减法操作,需要卖出去,赚钱,但不希望别人看到自己的源代码实现(函数的实现),需要把sub.c——>sub.lib

具体操作:项目名—>右击“属性”—>点击“配置类型”—>点击“应用程序”—>点击“静态库(.lib)"

.lib为静态库,导入静态库,别人打开后里面的内容看不懂。

函数递归

递归分为递推和回归

由函数的调用堆栈以及函数栈帧的创建与销毁可知,每一次函数调用都要只在栈区上开辟一块空间用来保存函数在调用过程中的上下文信息。

每一次回归时,函数调用时被分配的空间都要被释放,还给操作系统,每一次递推时,都需要在栈上开辟内存空间

递归定义

递归定义:

程序自己调用自己,一个过程或者函数在其定义或者说明中有直接或间接调用自身,把一个大型问题层层转化为与原问题相似但规模较小的问题进行求解,只需要少量的程序就可以描述出题解过程中所需要的多次重复计算,大大减少了代码量。

递归思考方式精髓:把大事化小,化小从简单思路入手

递归的两个必要条件

1.存在限制条件 ,当满足这个限制条件时,递归便不再继续;
2.每次递归调用都会无限接近这个限制条件
若两个必要条件缺一,会导致死递归,出现栈溢出现象。

递归与迭代

迭代定义

迭代定义:又称为循环

在完成同一个功能实现时,有递归与迭代两者方法实现,此时需要考虑使用递归时是否存在递归的两个必要条件、是否会出现栈溢出现象,若出现栈溢出,则用迭代实现,减少空间的使用,从而大大提高效率。

栈溢出现象:系统分配给程序的栈空间是有限制的,如果出现了死循环或者死递归,就会导致一直开辟栈空间,一直在使用栈空间,最终使栈空间耗尽。

解决栈溢出问题的方法:

1.将递归改写成非递归,比如:使用迭代的方法实现;

2.使用静态变量对象代替非静态变量对象(原因:既可以减少每次递归调用在栈上开辟的空间、返回时产生和释放栈对象的开哨,并且静态对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

递归与迭代编程题以及详细图解

打印一个整形值(无符号)的每一位(递归实现)

思路:因为我们可以很容易获得一个数的每一位,只需要%10、/10相互循环,直到该数为0停止,先打印123,在打印4(为先后关系 ,不是选择关系)。
在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void print(int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%d ", n % 10);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	print(n);
	return 0;
}

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

求字符串长度(递归实现)

思路:因为我们很容易拿到第一个字符,判断第一个字符是否为’\0’,若不为‘\0’,则个数至少为1,再继续判断后面的字符(为选择关系,不是先后关系)。
在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int mystrlen(char* s)
{
	if (*s != 0)
	{
		return mystrlen(s + 1)+1;
	}
	else
	return 0;
}
int main()
{
	char arr[] = "abcde";
	int ret = mystrlen(arr);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
铁铁们, 函数与递归讲解就到此结束啦,请动动你们的手给作者点个👍鼓励吧,你们的鼓励就是我的动力✨

  • 52
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 35
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值