前言
以下均使用visual Stdio 2022集成开发环境,测试环境为windows 11 ,debug版本64位操作数,内容仅供初学者参考。
目录
一、函数
在计算机科学中函数也称为子程序:它是一个大型程序中的某部分代码, 由一个或多个语句块组 成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。
Ⅰ、函数种类(库函数 + 自定义函数)
库函数:库函数是针对程序员对一些常见操作(比如最常见的printf)做一些包装,目的是为了让每一个程序员开发程序的时候都能用到,而为了支持可移植性和程序的效率,C语言官方提供了很多函数供程序员使用,这些提供的函数就是标准库函数。而函数分别被包装在指定的包里面(c语言里叫做头文件),例如scanf 和 printf函数被包装在stdio.h头文件里,如果需要使用这些函数则需要导入对应的头文件。
自定义函数:自定义函数就是程序员自己定义的函数,有参数,有返回值,使用函数可以使代码更加有层次感,也给程序员足够发挥空间。
Ⅱ、函数如何定义
返回值类型 函数名 (参数列表){
//函数体
}
例如定义一个func函数返回n的阶乘:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
int func(int n);
int a = 5;
int b = func(a);
printf("%d\n",b);
return 0;
}
int func(int n) {
int sum = 1;
for (int i = 1; i <= n; i++) {
sum = sum * i;
}
return sum;
}
①、参数种类
定义函数中的参数列表可以自定义数量,定义的参数用 ‘,’分隔开。
而参数分为两种:形式参数 + 实际参数
实际参数:实际参数是在使用函数的时候传递的变量,如上述代码中的a就是实际参数
形式参数:形式参数是在函数中的变量,如上述代码中的n就是形式参数
为什么要区分这两个参数呢?请看下述代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
void func(int n);
int a = 5;
func(a);
printf("%d", a);
return 0;
}
void func(int n) {
n = 100;
}
我们在函数里面修改了n的值,但是输出却是5。因为变量n是a的一份临时拷贝,改变n并不会对a产生影响。
想把这块理解得更深入 ————<函数栈帧的创建和销毁(创作中)>
②、 返回值
返回值类型是基于函数的功能而定的,例如我们想返回一个数字,就用int 或者long作为函数的返回类型,我们想函数返回一个字符,则用char。等等
③、传值调用
传值调用中参数传递的是数值类型,形参改变并不会影响实参。(就跟上面的例子一样)
④、传址调用
传值调用中参数传递为指针类型,指针说白了就是一串数字,并且这串数字指向了一个数据(房间)。我们在形参中修改这个指针(房间号),实参(房间里的人)并不会被影响,但是如果我们修改了形参指针指向的那一段数据(让那个房间里的人出来),那么这段数据的修改在实参中是会被影响的(房间里的人已经受影响了)。这是很多程序员会犯的错误!
举个例子:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
void func(int* n);
int i = 0;
int* a = &i;
func(a);
printf("%d", i);
return 0;
}
void func(int* n) {
*n = 100;
}
可以看到我们在func函数中成功修改了实参!任何传址调用,都有一定的风险,我们在敲代码的时候要小心这个坑。
Ⅲ、函数声明
在上述代码中可以看到main函数中开头就是一个void func(int* n); 这便是函数声明,在默认情况下,Visual Stdio 的扫描顺序是由上而下的,如果我们的函数定义在main函数后面,则需要声明在main函数里面,如果是定义在main函数上面,则不用声明。但是还有另外一种结构:声明在别的头文件,比如我们创建一个写main函数的c源文件,一个用来存放资源的头文件,还可以有一个专门写函数的c源文件,那么如何把这三个文件连接起来呢?
Ⅳ、函数递归
原理:递归的本质就是函数调用自身
例:
int main() {
int a = fun(5);
printf("%d", a);
return 0;
}
//使用递归计算n的阶乘
int fun(int n) {
if (n != 0) {
return n * fun(n - 1);
}
return 1;
}
自身调用自身? 那不是无限调用嘛!?
所以为了能使用有效的递归,我们便给递归加上了这三个枷锁:
①、递归的规模必须越来越小
②、递归必须有终止条件
③、函数的返回值类型与参数类型相同