先赞后看,不足指正!
这将对我有很大的帮助!
所属专栏:C语言知识
阿哇旭的主页:Awas-Home page
目录
引言
相信大家对函数的概念并不陌生,在我们数学学习中经常穿插着这个概念,比如,一次函数y=kx+b,k和b都是常数,我们给一个任意的x,就可以得到y的值。
那么,话不多说,我们一起来看看吧!
1. 函数的概念
在C语言中引入了函数(function)的概念,也称作子程序,而构成C程序的基本单元是函数,函数中包含程序的可执行代码。同时一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。
我们可以举一个例子让大家更好地理解函数:
例如盖一栋楼房,在工程师的指挥下,有工人搬运建造材料,有工人建造房屋结构,有工人粉刷内外墙壁,各司其职。编写程序与盖楼的道理是差不多的,主函数就像工程师,控制每一步程序的执行,而子函数就像工人,分别去完成自己的工作。
#include<stdio.h>
// 执行搬运功能
void Move()
{
printf("搬运建筑材料");
}
// 执行建造功能
void Build()
{
printf("建造房屋结构");
}
// 执行粉刷功能
void Paint()
{
printf("粉刷内外墙壁");
}
int main()
{
Move(); // 执行搬运函数
Build(); // 执行建造函数
Paint(); // 执行粉刷函数
return 0;
}
2. 函数的类型
2.1 库函数
C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库,那不同的编译器厂商根据ANSI提供的C语言标准就给出了一系列函数的实现。这些函数就被称为库函数。
比如,我们经常使用的 printf 、scanf 都是库函数,而库函数也是函数,通常使用库函数都需要包含对应的头文件,不要忘记哦!
库函数的学习和查看工具很多,比如:
(1) 了解更多库函数头文件
(2) cplusplus.com
2.2 自定义函数
自定义函数是指由程序员自己编写的、用于执行特定任务的函数。而自定义函数更加灵活,也能给程序员写代码更多的创造性。
2.2.1 语法
自定义函数和库函数是一样的,都有函数返回类型、函数名、形式参数以及函数体,具体形式如下:
datatype fun_Name(形式参数)
{
fun_body
}
- datatype是函数返回值类型,可以是int 、char、float等,有时候返回类型可以是 void ,表示什么都不返回。
- fun_Name是函数名,也是函数的标识符,遵循标识符的命名规则。
- 形式参数是函数定义中的一部分,它用于接收传递给函数的值。参数可以是任意合法的数据类型,例如整数、浮点数、字符、指针等。在函数体中,我们可以使用参数的值来执行相应的操作。
- { fun_body }括起来的部分被称为函数体,是函数定义的主要部分,它包含了一系列的语句和表达式,用于实现函数的具体功能。函数体中的代码会按照定义的顺序被执行。
- 返回值是函数执行完毕后返回给调用函数的结果。在函数体中,我们可以使用return语句将结果返回给调用函数的地方。返回值的类型必须与函数头中指定的返回类型相匹配。
2.2.2 举例
自定义一个加法函数,完成两个整形变量的相加操作:
#include<stdio.h>
int Add(int x, int y)
/*int ->函数返回类型
* Add ->自定义函数名称
* a、b是形式参数,均是int型
*/
{
int sum = 0;
sum = x + y;
return sum;
}
int main()
{
int a = 0;
int b = 0;
// 注意输入内容与格式化内容一致
scanf("%d %d", &a, &b);
int ret = Add(a, b);
printf("a + b = %d\n", ret);
return 0;
}
输入:22 17
输出:x + y = 39
当然,我们可以把Add函数简化一下:
int Add(int x, int y)
{
return x + y; // 直接返回结果
}
3. 函数的参数
3.1 实参
实参,也称为实际参数,也就是主函数main调用子函数时,传递给函数的参数a和b,而实际参数就是真实传递给函数的参数。
int ret = Add(a, b); // 加法函数举例
3.2 形参
形参,也称为形式参数,也就是在函数名 Add 后的括号中写的 x 和 y,在函数未调用时不会向内存申请空间,形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。
int Add(int x, int y) // 加法函数举例
{
}
3.3 实参与形参的关系
我们来观察下列代码,来探究二者之间的关系:
#include<stdio.h>
int Swap(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
// 注意输入内容与格式化内容一致
scanf("%d %d", &a, &b);
printf("交换前:a=%d, b=%d\n", a, b);
Swap(a, b);
printf("交换后:a=%d, b=%d\n", a, b);
return 0;
}
输入:1 5
输出:交换前:a=1, b=5 交换后:a=1,b=5
我们可以观察到,交换前后的结果相同,为什么会这样?我们可以通过调试来发现问题。
通过调试,我们不难发现,x和y的值确实发生了交换,但并未影响到a和b,是因为x和y的地址和a和b的地址是不一样的,在各自的内存空间内互不干扰。
当函数调用结束要返回的时候,前面创建的函数栈帧也要销毁。这里包含函数栈帧的创建和销毁的知识,我会在后面的章节为大家讲解,这样理解起来就更容易。
由此,我们可以得出一个结论:形参是实参的一份临时拷贝,改变形参并不会影响到实参。
4. 数组作为函数的参数
写一个函数将一个整型数组的内容,全部重置为0,再写一个函数打印重置后数组的内容。
#include<stdio.h>
// 重置数组函数
void Set_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
arr[i] = 0;
}
}
// 打印数组函数
void Print_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int size = sizeof(arr) / sizeof(arr[0]);
Print_arr(arr, size); // 重置前
Set_arr(arr, size); // 重置数组
Print_arr(arr, size); // 重置后
return 0;
}
需要我们注意的是, Set_arr函数要能够对数组内容重置,就得把数组作为参数传递给函数,同时函数内部在设置数组每个元素的时候,也得遍历数组,需要知道数组的元素个数。所以我们需要给Set_arr传递2个参数,一个是数组,另外一个是数组的元素个数。Print_arr函数也是这样,只有得到了数组和元素个数,才能遍历打印数组的每个元素。
5. return 语句
在函数中,也会经常使用return语句,下面是我们要注意的几点:
- return后边可以是一个数值,也可以是⼀个表达式,如果是表达式则先执行表达式,再返回表达式 的结果。
- return后边也可以什么都没有,直接写return; 这种写法适合函数返回类型是void的情况。
- return返回的值和函数返回类型不一致,系统会自动将返回的值隐式转换为函数的返回类型。 return语句执行后,函数就彻底返回,后边的代码不再执行。
- 如果函数中存在if等分支的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
6. 嵌套调用和链式访问
6.1 函数的嵌套调用
在C语言中,我们学习了分支和循环结构的嵌套使用,而函数嵌套调用就是函数之间的互相调用。
我们可以举一个简单的例子:
#include <stdio.h>
void func1()
{
printf("函数1\n");
}
void func2()
{
printf("函数2\n");
func1(); // 在函数2中调用函数1
}
void func3()
{
printf("函数3\n");
func2(); // 在函数3中调用函数2
}
int main()
{
printf("主函数\n");
func3(); // 在主函数中调用函数3
return 0;
}
因此,当我们运行这段代码时,会按照函数的嵌套调用顺序,依次输出以下内容:
主函数
函数3
函数2
函数1
在上面的代码中,主函数main使用库函数printf,也是属于函数嵌套调用。
注:函数之间可以嵌套调用,但是不能嵌套定义。
6.2 函数的链式访问
所谓链式访问就是将一个函数的返回值作为另外一个函数的参数,像链条一样将函数串起来就是函数的链式访问。
#include <stdio.h>
#include <string.h>
int main()
{
printf("%d\n", strlen("abcdef"));//链式访问
return 0;
}
输出:6
我们可以观察下列代码,猜测输出结果是什么?
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
输出:4321
为什么呢?我在第一次遇见也卡住了。
注:printf函数返回的是打印在屏幕上的字符的个数。
从上面代码中,可以知道第一个printf打印的是第二个printf的返回值,第二个printf打印的是第三个 printf的返回值。
第三个printf打印43,在屏幕上打印2个字符,再返回2;
第二个printf打印2,在屏幕上打印1个字符,再返回1;
第一个printf打印1,所以最终输出:4321。
7. 函数的声明和定义
7. 1 单个文件
7.1.1 函数定义在函数调用前
#include<stdio.h>
// 函数定义
int Add(int x, int y)
{
int sum = 0;
sum = x + y;
return sum;
}
int main()
{
int a = 0;
int b = 0;
// 注意输入内容与格式化内容一致
scanf("%d %d", &a, &b);
int ret = Add(a, b); // 函数调用
printf("a + b = %d\n", ret);
return 0;
}
7.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("a + b = %d\n", ret);
return 0;
}
// 函数定义
int Add(int x, int y)
{
int sum = 0;
sum = x + y;
return sum;
}
在函数调用之前先声明一下这个函数,这样代码就能正常编译运行。
7.2 多个文件
在大项目中,代码可能比较多,通常不会将所有的代码都放在一个文件中;我们往往会根据程序的功能,将代码拆分放在多个文件中,这样有助于提高代码的可读性、可维护性和可扩展性。
一般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在源文件(.c)中。
应用举例:~扫雷游戏来啦~
8. 结语
希望这篇文章对大家有所帮助,如果你有任何问题和建议,欢迎在评论区留言,这将对我有很大的帮助。
完结!咻~