在讲之前再讲一下代码要规范:如下
//代码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、每一次递推越来越接近限制条件