目录
5. 函数的嵌套调用和链式访问
5.1 嵌套调用
5.2 链式访问
6. 函数的声明和定义
6.1 函数声明
6.2 函数定义
7. 函数递归
7.1 什么是递归?
7.2 递归的两个必要条件
在讲之前再讲一下代码要规范:如下
//代码1
#include<stdio.h>
test()
{
printf("hehe\n");
}
int main()
{
int ret=test();
printf("%d\n", ret);
return 0;
}
我们可以看出test是有返回值的。
(1)C语言规定:函数的返回类型不写的时候,默认是返回的是int类型。
建议:真的不需要函数返回值的时候,明确写void。
(2)为什么返回的是5呢?(我们现在知道就行,不用深入了解)
①除main外,其余函数没有写return,取决于编译器的实现(返回的一般,是函数执行完的最后一条指令所产生的结果)
②main,没写return,默认返回0
(3)printf函数的返回值是打印在屏幕上字符的个数。
//代码2
#include<stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
test(100);
return 0;
}
如上:虽可运行,但不严谨
C的不严谨:不需要传参时,应写void;否则完全可能传参,只是函数没有接收。
5. 函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的
5.1 嵌套调用
函数的嵌套调用,即在一个被调函数内部,又调用了其他的函数。
1、代码演示:
//嵌套调用
#include<stdio.h>
void new_line(void)
{
printf("hehe\n");
}
void three_line(void)
{
int i = 0;
for (i = 0; i < 3; i++)
{
new_line();//调用new_line三次
}
}
int main()
{
three_line();//调用three_line
return 0;
}
2、图示如下:
注:函数可以嵌套调用,但是不能嵌套定义
5.2 链式访问
把函数的返回值作为另一个函数的参数。
例子:
//代码1
#include<stdio.h>
#include<string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(arr);//计算字符串长度,注统计‘\0’之前的字符个数
printf("%d\n", ret);
printf("%d\n", strlen(arr));//strlen的返回值作为printf的参数
return 0;
}
如上,我们应该能体会到什么是链式访问了。
我们再举一个链式访问的经典例子:
//代码2
#include<stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//printf的返回值是打印在屏幕上字符的个数
return 0;
}
tip:printf函数的返回值是打印在屏幕上字符的个数。
程序输出:4321
6. 函数的声明和定义
6.1 函数的声明
1、告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。(声明只是告诉你有,但是真的有没有取决于定义)
声明的一般形式如下:
①返回类型 函数名(形参列表); 如:int Add(int x,int y);
或
②返回类型 函数名(形参类型列表); 如:int Add(int ,int );
好的风格:建议使用①;
注:函数的声明以“;”结束,不能省略。
2、函数的声明一般出现在函数的使用之前,要满足先声明后使用。
3、函数的声明一般放在头文件中的
6.2 函数的定义
函数的定义是指函数的具体实现,交代函数的功能实现
说明:函数为什么要声明?
代码在编译的时候,要进行代码的扫描,是顺序从前往后扫描的。
1、当函数定义出现在函数调用之后时,在主调函数前,采用函数原型对被调用函数进行声明。
当不声明,编译器会警告,如下:
2、函数定义出现在函数调用之前,可不用进行函数声明。(函数的定义也是一种特殊的声明)
#include<stdio.h>
//函数的声明
int Add(int x, int y);
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int ret = Add(a, b);
printf("%d", ret);
return 0;
}
//函数的定义
int Add(int x, int y)
{
return x + y;
}
这就学会了函数的定义和声明吗?
函数的声明和定义,不是这样用的,以上只是它的一种运用场景,只是它的语法展示,真正在工程中,函数的定义和声明,我们是怎么写的呢?
头文件:.h——放置函数的声明
源文件:.c——放置函数的实现(定义)
如要写一个函数实现求两个整数的和:
1、创建一个源文件:add.c——放置函数的实现(定义)
2、创建一个头文件:add.h——放置函数的声明
3、测试——再创建一个源文件:test.c
在工程中想要使用(调用)Add函数,也是如库函数一样要打招呼。
库函数:使用库函数,必须包含 #include 对应的头文件。
自定义函数:也如库一样,必须包含#include对应的头文件。
如上截图:库函数的头文件名一般用<>引用,自定义函数的头文件名一般用“”引用。
我们本可以在一个.c文件写的,为什么分成三个文件呢?好处是什么呢?
1、模块化开发(分工)
例:要实现一个计算机
加法——a(程序员做)——add.c add.h
减法——b——sub.c sub.h
乘法——c——mul.c mul.h
除法——d——div.c div.h
如果不分开,模式化开发,在一个.c文件a、b、c、d四个人怎么同时写了?,还会使代码变得混乱。
分开,模式化开发每个人都可以同时写,写完后再组成一个.c文件就完成了计算机了。
2、代码的隐藏(可以使用,但不知道源代码)
(1)具体步骤:项目名——属性——配置属性——常规——配置类型——改成静态库——应用——确定——ctrl+f7——在文件中生成xx.lib文件——用记事本查看——显示乱码
(2)在另一个项目使用:需要导入xx.lib和xx.h文件
还需要在主调函数前:#pragma comment(lib,"xx.lib"),就可使用了
现在知道不再一个.c文件写有隐藏代码的好处就行了,不用深入理解,后期我会在博客中写的。
总结:
函数的声明我们一般放置在头文件中
函数的实现我们都是放置在源文件中
7. 函数的递归
7.1 什么是递归
程序调用自身的编码技巧称为递归(recursion)。
递归做为一种算法在程序设计语言中广泛应用。一个函数(或过程)在其定义(或说明)中 有直接或间接调用自身 的一种方法。它通常把一个 大型复杂 的问题 层层转化 为一个与原问题 相似 的规模较小的问题来求解。递归策略 , 只需少量 的程序就可 描述出解题过程 所需要的 多次重复计算 ,大大地 减少 了程序的代码量。递归的主要思考方式在于:把大事化小
#include<stdio.h>
int main()
{
printf("hehe\n");
main();//递归:在函数内自己调用自己
return 0;
}
f10调试:
7.2 递归的两个必要条件
1、存在限制条件,当满足这个限制条件的时候,递归便不继续(递归出口)
2、每次递归调用之后越来越接近这个限制条件
7.2.1 练习1
接受一个整型值(无符号),按照顺序打印它的每一位。例如:输入: 1234 ,输出1 2 3 4
void print(unsigned int n)
{
if (n > 9)//限制条件
{
print(n / 10);//递归调整,接近限制条件
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%d", &num);//1234
//写一个函数打印num的每一位
print(num);
return 0;
}
递归的理解:
递——递推
归——回归
先递推后回归。
7.2.2 练习2
编写函数不允许创建临时变量,求字符串的长度。
#include<stdio.h>
int my_strlen(char* str)
{
if (*str != '\0')
{
return 1 + my_strlen(str + 1);//str+1,字符指针+1,向后跳一个字符
}
else
{
return 0;
}
}
int main()
{
char arr[] = "bit";
int ret = my_strlen(arr);//数组名其实是首元素地址
printf("%d\n", ret);
return 0;
}
(1)数组名其实是首元素的地址
(2)字符指针+1,向后跳一个字符。(那整形指针+1,向后跳四个字符)
总结:递归一般都要有两个必要条件(一般与if搭配)
1、有限制条件
2、每一次递推越来越接近限制条件