程序猿入门攻略(八)——函数

本文介绍了C语言中的函数使用,包括函数的嵌套调用、链式访问,强调了strlen与sizeof的区别。讨论了函数的声明与定义,以及递归函数的概念、必要条件和递归与迭代的应用。通过实例解析了递归在计算阶乘和斐波那契数列中的应用,并探讨了栈溢出问题及解决方案。
摘要由CSDN通过智能技术生成

5、函数的嵌套调用和链式访问

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

5.1嵌套调用

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

image.png
注:函数可以嵌套使用,但是不可以嵌套定义。

5.2链式访问

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

#include <stdio.h>
#include <string.h>

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

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

image.png
image.png

tip:strlen 和 sizeof 的区别

  1. sizeof是运算符(C++关键字),strlen是函数。
  2. sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以'\0'结尾的。
  3. sizeof计算的是变量的大小,而strlen计算的是字符串的长度,前者不受字符'\0'影响,后者以'\0'作为长度判断的依据。

测试代码:

#include <stdio.h>
#include <string.h>
int main()
{
    char* str1 = "abcdef";
    char str2[] = "abcdef";
    char str3[8] = { 'a', 'b', 'c' };
    char str4[] = "ab\0cd";

    printf("sizeof(str1)=%d\n", sizeof(str1));
    printf("strlen(str1)=%d\n", strlen(str1));

    printf("sizeof(str2)=%d\n", sizeof(str2));
    printf("strlen(str2)=%d\n", strlen(str2));

    printf("sizeof(str3)=%d\n", sizeof(str3));
    printf("strlen(str3)=%d\n", strlen(str3));

    printf("sizeof(str4)=%d\n", sizeof(str4));
    printf("strlen(str4)=%d\n", strlen(str4));

    return 0;
}

执行结果:
image.png
结果解释:
1、str1是字符指针变量,sizeof获得的是该指针所占的地址空间,32位操作系统对应4字节,所以结果是4;
strlen返回的是该字符串的长度,遇到’\0’结束,‘\0’本身不计算在内,故结果是6。
2、str2是字符数组,没有规定大小,所以大小由字符串常量"abcdef"确定,sizeof获得该数组所占内存空间大小,包括字符串结尾的’\0’,所以结果为7;
strlen同理,则是6。
3、str3也是字符数组,但数组的大小已经确定为8,故sizeof得到的结果是8;
strlen统计’\0’之前所有字符的个数,即为3。
4、str4是常量字符数组,和str2类似,区别在于中间忽然出现个’\0’,所以sizeof得到字符总数6;
strlen计算至’\0’结束,因此返回2。
注意:这里的’\0’是一个字符,别看成两个。


6、函数的声明和定义

6.1函数声明

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

6.2函数的定义

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


7、递归函数

7.1什么是递归?

程序调用自身的编程技巧成为递归(recursion)。

递归作为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,
它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。

递归的主要思考方式在于:把大事化小

7.2递归的两个必要条件

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

例1:

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

#include <stdio.h>

void print(unsigned int n)
{
	if (n > 9)//限制条件
	{
		print(n / 10);//递归,在循环结束前,每次调用自己
	}
	printf("%d ", n % 10);
}

int main()
{
	unsigned int num = 0;
	scanf("%u", &num);
	print(num);

	return 0;
}

tip
%d是打印有符号的整数(正负数)
%u是打印无符号的整数(非负数)

例2:

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

//方法一(用临时变量)
#include <stdio.h>

//int my_strlen(char str[])//参数部分写成数组的形式
int my_strlen(char* str)//参数部分写成指针形式;以上两种写法均可
{
	int count = 0;//计数,,临时变量
	while (*str != '\0')
	{
		count++;
		str++;//找下一个字符
	}
	return count;
}

int main()
{
	char arr[] = "abc";//['a','b','c','\0']
	int len = my_strlen(arr);
	
	printf("%d\n",len);
	return 0;
}

//方法二(递归实现,不用临时变量)
#include <stdio.h>

int my_strlen(char* str)
{
	if(*str != '\0')
		return 1 + my_strlen(str + 1);
	else
		return 0;
}

int main()
{
	char arr[] = "abc";//['a','b','c','\0']
	int len = my_strlen(arr);
	
	printf("%d\n",len);
	return 0;
}

注:方法二中,my_strlen函数一共被调用了四次,直到接收到’\0’后停止调用,
此处的’\0’便是递归函数的限制条件。
如果没有限制条件,会死递归,程序崩溃。

7.3 递归与迭代

例3:

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

#include <stdio.h>

int factorial(int n)
{
	if (n <= 1)
	{
		return 1;
	}
	else
		return n * factorial(n - 1);
}

int main()
{
	int i = 0;
	scanf("%d", &i);
	int ret = factorial(i);
	printf("%d\n", ret);
	return 0;
}

例4:

求第n个斐波那契数(不考虑溢出)。
斐波那契数列指的是这样一个数列:1,1,2,3,5,8,13,21,34,55,89…
这个数列从第3项开始,每一项都等于前两项之和。

#include <stdio.h>

int fib(int n)
{
	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n - 2);
}

int main()
{
	int i = 0;
	scanf("%d", &i);
	int ret = fib(i);

	printf("%d\n", ret);
	return 0;
}

虽然以上代码逻辑没问题,但是运行却有一些问题:

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

为什么呢?
我们发现fib函数在调用的过程中很多计算其实是一直重复的。
如果我们把代码改一下:

int count = 0;//全局变量
int fib(int n)
{
	if (n == 3)
		count++;
	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n - 2);
}

最后我们输出看看count,是一个很大的值。

那我们如何改进呢?

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

那如何解决上述的问题:

  1. 将递归改写为非递归
  2. 在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

比如,下面的代码就采用了非递归的方式来实现:

//求n的阶乘
int factorial(int n)
{
	int result = 1;
	while (n > 1)
	{
		result *= n;
		n -= 1;
	}
	return result;
}

//求第n个斐波那契数
int fib(int n)
{
	int result;
	int pre_result;
	int next_older_result;
	result = pre_result = 1;
	while (n > 2)
	{
		n -= 1;
		next_older_result = pre_result;
		pre_result = result;
		result = pre_result + next_older_result;
	}
	return result;
}

提示:

  1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  3. 当一个问题相当复杂,难以迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值