【C语言】函数讲解(下)

所属专栏:C语言
博主首页:初阳785
代码托管:chuyang785
感谢大家的支持,您的点赞和关注是对我最大的支持!!!
博主也会更加的努力,创作出更优质的博文!!
关注我,关注我,关注我,重要的事情说三遍!!!!!!!!

1.函数的声明和定义

1.1函数声明

  1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
  2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
  3. 函数的声明一般要放在头文件中的(以.h结尾的文件)。

比如我们写一个加法函数:
在这里插入图片描述

1.2函数定义

函数的定义是指函数的具体实现,交待函数的功能实现。
而函数的定义一般放在以.h 为后缀的文件中的。
在这里插入图片描述
而这里其实重点在于为什么要把函数的声明和定义都写在不同的地方呢?
其实这种写法一般都只出现在一些大型的程序开发中出现的。因为一个大型的程序开发肯定不只有一个人编写代码的,而是由很多人一起编写,最后再把他们链接在一起的,而程序的开发都是由众多的函数嵌套所组成的,如果说我们把函数的声明和定义都写在一个文件里面的话那么一次只能由一个人编写,不但会影响代码的编写速度还很有可能一位每个人的编写代码风格不同导致程序出现错误,所以才要分开写。

当然我们函数声明和函数定义也可以写在同一个文件里面:
第一种写法:
在这里插入图片描述
我们必须要保证的是函数的声明必须在函数的定义之前
第二种写法:
在这里插入图片描述

2.函数的嵌套调用和链式访问

2.1嵌套调用

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

#include <stdio.h>
void line()
{
 printf("hehe\n");
}
void _line()
{
    int i = 0;
 for(i=0; i<3; i++)
   {
        line();
   }
}
int main()
{
 	_line();
 	return 0;
}

就是说函数里面可以在嵌套一个函数,类似于套娃。
但是我们要注意的是我们的函数可以嵌套使用,但是不能嵌套定义。
在这里插入图片描述
这样写是打错的错的。

2.2链式访问

把一个函数的返回值作为另外一个函数的参数。
一般我们计算字符串长度的时候会这样写:

int main()
{
    char arr[20] = "hello";
 	int ret = strlen(arr));
 	printf("%d\n", ret);
 	return 0;
}

这里我们介绍一下strlen函数:
在这里插入图片描述
它是专门用来计算字符串长度的函数,遇到’\0’停止。(后期会详细讲到)
所包含的头文件是#include<string.h>

我们也可以使用函数链式访问来写:

int main()
{
    char arr[20] = "hello";
 	printf("%d\n", strlen(arr));
 	return 0;
}

这个和上面的结果是一样的。
我们再来看下面一串代码:

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

我们看一下这个结果是什么,这个时候我们就要知道printf的返回值是什么:
在这里插入图片描述
这里说了,返回所包含字符的个数。
我们看一下结果:
在这里插入图片描述
这里先打印43;在打印了printf(“%d”, printf(“%d”, 43))也就是打印printf(“%d”, 43)的返回值,而43是两个字符所以返回的是2,所以打印出来了2;再就是 printf(“%d”, printf(“%d”, printf(“%d”, 43)))打印 printf(“%d”, printf(“%d”, 43))的返回值,在上面我们知道了 printf(“%d”, 43)的返回值是2,那么 printf(“%d”, printf(“%d”, 43))== printf(“%d”, 2)自然它的返回值就是1,所以就打印出来了1。

3.函数递归

3.1什么是递归

程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问
题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程
序的代码量。
递归的主要思考方式在于:把大事化小

3.2递归的两个必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件

3.2.1练习1

接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4

void print(int n)
{
	if (n == 0)
		return;
	print(n/10);
	printf("%d", n%10);
}
int main()	
{
	int num = 1234;
	print(num);
}

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

3.2.2练习2

编写函数不允许创建临时变量,求字符串的长度。

#include <stdio.h>
int Strlen(const char*str)
{
 if(*str == '\0')
 	return 0;
 else
      return 1+Strlen(str+1);
}
int main()
{
 	char *p = "abc";
 	int len = Strlen(p);
 	printf("%d\n", len);
 	return 0;
}

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

3.3递归与迭代

3.3.1练习1

求n的阶乘。(不考虑溢出)

int factorial(int n)
{
 	if(n <= 1)
 		return 1;
 	else
 		return n * factorial(n-1);
}
int main()
{
	int n=0;
	scanf("%d",&n);
	printf("%d",factorial(n));
	return 0;
}

3.3.2练习2

求第n个斐波那契数。(不考虑溢出)

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);
	printf("%d",fib(n));
	return 0;
}

但是我们发现有问题;

  • 使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。
  • 使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。

在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)
这样的信息。
系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一
直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。

那如何解决上述的问题:

  1. 将递归改写成非递归。
  2. 使用 static 对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代
    nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放
    nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层
    所访问。
  3. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  4. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  5. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

初阳hacker

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

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

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

打赏作者

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

抵扣说明:

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

余额充值