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;
}
注:函数可以嵌套使用,但是不可以嵌套定义。
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;
}
tip:strlen 和 sizeof 的区别
sizeof
是运算符(C++关键字),strlen
是函数。sizeof
可以用类型做参数,strlen
只能用char*
做参数,且必须是以'\0'
结尾的。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;
}
执行结果:
结果解释:
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
(栈溢出)这样的信息。- 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者死递归,这样有可能导致一直开辟栈空间,最终导致栈空间耗尽的情况,这样的现象我们称为栈溢出。
那如何解决上述的问题:
- 将递归改写为非递归
- 在递归函数设计中,可以使用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;
}
提示:
- 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
- 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- 当一个问题相当复杂,难以迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行开销。