目录
一、函数的概念
- 函数是 C 语言中非常重要的概念,它是将一段具有特定功能的代码块进行封装,以便重复使用和管理。
- 在C语⾔中⼀般会用到两类函数:
• 库函数
• 自定义函数
二、库函数
1、标准库和头文件
- C语言给我们规定了各种语法规则,但不提供库函数;国际标准的ANSI C规定了一些常用的函数的标准,被称为标准库。
- 不同的编译器厂商根据ANSI提供的C语言标准,将一系列函数进行实现,这些函数就被称为库函数。
- C语言中库函数相关头文件资料可参考:C 标准库头文件
2、库函数的一般格式
- 以一个简单的数学库函数 sqrt() 为例,其一般格式为:
result = sqrt(value);
- 其中 sqrt() 是函数名, value 是要计算平方根的数值, result 是得到的结果。不同的库函数有不同的参数要求和返回值类型,需要根据具体函数的文档进行正确使用。
3、库函数的使用方法
- 库函数的学习和工具有很多,可以通过以下网址进行学习:
- C/C++官⽅的链接:https://zh.cppreference.com/w/c/header
- cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
- 示例:
- 参照以上资料,我们使用 fmaf 函数来进行举例:
代码示例:
int main(void)
{
float x = 0.0;
float y = 0.0;
float z = 0.0;
printf("输入三个数值:\n");
scanf("%f %f %f", &x, &y, &z);
printf("(x*y)+z=%f", fmaf(x,y,z));
return 0;
}
运算结果:
4、库函数文档的一般格式
- 1、函数原型
- 2、函数功能介绍
- 3、参数和返回类型说明
- 4、代码举例
- 5、代码输出
- 6、相关知识链接
三、自定义函数
1、函数的语法形式
- 自定义函数是程序员自行创造,能带给代码更多的可能性,形式如下:
ret_type fun_name(形式参数)
{
}
• ret_type 是函数返回类型
• fun_name 是函数名
• 括号中放的是形式参数
• {}括起来的是函数体
- 函数就像是一个加⼯⼚,参数就是输入的原材料,经过函数内的计算,得出结果。
• ret_type 是⽤来表⽰函数计算结果的类型。
• fun_name 是为了⽅便使⽤函数;就像工厂的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调⽤。
• 函数的参数就相当于,⼯⼚中送进去的原材料,要交代清楚参数的类型和名字,以及参数个数。
• {} 括起来的部分被称为函数体,函数体就是完成计算的过程。
2、函数的举例
例 1:计算两个数之和的函数
#include <stdio.h>
int add(int num1, int num2)
{
return num1 + num2;
}
int main()
{
int a = 5, b = 3;
int result = add(a, b);
printf("结果: %d\n", result);
return 0;
}
例 2:判断一个数是否为偶数的函数
#include <stdio.h>
int isEven(int num)
{
if (num % 2 == 0)
{
return 1;
}
else
{
return 0;
}
}
int main() {
int num = 7;
if (isEven(num))
{
printf("%d 是偶数\n", num);
}
else
{
printf("%d 不是偶数\n", num);
}
return 0;
}
四、形参和实参
- 在函数的使用中,把函数的参数分为实参和形参。
- 例如:
#include <stdio.h>
int add(int num1, int num2)
{
return num1 + num2;
}
int main()
{
int a = 5, b = 3;
int result = add(a, b);
printf("结果: %d\n", result);
return 0;
}
1、实际参数(实参)
上述代码中,int a = 5, b = 3;对a、b进行了赋值,下一步调用add函数时,将a和b的值传递给了函数,称之为实际参数,简称实参。
实际参数就是真实传递给函数的参数
2、形式参数(形参)
- 在定义add函数的时候,在括号中写的 int num1、int num2,称之为形式参数,简称形参。
- 为什么称之为形式参数呢?因为我们虽然定义了add函数,但如果我们不调用,add函数中的num1和num2只是形式上的,不会向内存申请空间,不会真实的存在。
- 形式参数在函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化。
3、实参和形参的关系
- 从之前例子可以看出,形参的值是实参传递过来的,虽然有着一定的联系,但形参和实参是两个不同的空间。
例如:
#include <stdio.h>
int add(int num1, int num2)
{
return num1 + num2;
}
int main()
{
int a = 5, b = 3;
int result = add(a, b);
printf("结果: %d\n", result);
return 0;
}
- 进入调试界面,可以发现,num1和num2得到了a和b传递过来的值,但地址却不一样,可见,在使用时,形参和实参是不同的参数。
- 可以理解为形参是实参的一份临时拷贝,函数结束地址就会被是释放掉。
五、return语句
- 在函数的设计和使用中,经常会使用到 return 语句,在使用时有几点注意事项:
- 1、return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果。
return a+b;
- 2、return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
return;
- 3、return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
int add(int x,int y)//定义返回值类型是int
{
float c = x + y;//接收x+y的值为float
return c; //这里C会被强制类型转换为int类型
}
- 4、return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
int add(int num1, int num2)
{
return num1 + num2; //return会带着num1+num2的返回值,跳出add函数
}
- 5、如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
#include <stdio.h>
int checkNumber(int num) {
if (num > 0)
{
return 1; // 正数情况有返回
}
else if (num == 0)
{
return 0; // 零的情况有返回
}
else
{
// 这里如果没有返回,就会导致编译错误
}
}
int main() {
int result = checkNumber(-1);
printf("结果: %d\n", result);
return 0;
}
- 在这个例子中,如果 checkNumber 函数中最后少了对 num<0 这种情况的返回,就会引发编译警告或错误。
六、数组做函数参数
1、数组做函数参数时的使用
- 在函数的使用中,难免会碰到需要将数组作为参数进行传递的操作,例如:
#include <stdio.h>
// 函数用于计算数组元素之和
int sumArray(int arr[], int size)
{
int sum = 0;
for (int i = 0; i < size; i++)
{
sum += arr[i];
}
return sum;
}
int main() {
int myArray[] = {10, 20, 30, 40, 50};
int n = sizeof(myArray) / sizeof(myArray[0]);
int total = sumArray(myArray, n);
printf("数组元素之和为: %d\n", total);
return 0;
}
在这个例子中:
- sumArray 函数接受一个整型数组和数组的大小作为参数。这里的数组参数实际上是一个指针,它指向数组的起始地址。
- 在 main 函数中,定义了一个数组 myArray ,并计算出数组的元素个数 n 。
- 然后调用 sumArray 函数,将数组和其大小传递进去,函数通过遍历数组计算元素之和并返回。
- 通过调试,我们可以发现,形参中的arr地址就是myArray的地址,也就是说,函数例操作的就是myArray数组。
2、使用数组传参时的注意事项
- 在使用数组时,有几点需要注意:
1、函数的形式参数要和函数的实参个数匹配
2、函数的实参是数组,形参也是可以写成数组形式的
3、形参如果是⼀维数组,数组⼤⼩可以省略不写
4、形参如果是⼆维数组,⾏可以省略,但是列不能省略
5、数组传参,形参是不会创建新的数组的
6、形参操作的数组和实参的数组是同⼀个数组
7、当数组作为函数参数传递时,在函数内部对数组的修改会影响到原始数组。同时,数组的大小需要另外传递,因为通过数组参数本身无法确定其实际大小。
七、嵌套调用和链式访问
1、嵌套调用
- 嵌套调用就是函数之间的调用,每一个函数就像一个模块,需要的时候可以直接拿过来装上使用。
- 例如:
#include <stdio.h>
// 函数 new_line 用于打印一行字符串 "hehe"
void new_line()
{
printf("HEHE\n");
}
// 函数 three_line 用于调用 new_line 函数打印三行 "HEHE"
void three_line()
{
int i = 0;
for (i = 0; i < 3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
- 上述代码中, new_line 函数用于打印一行字符串"HEHE", three_line 函数用于调用 new_line 函数打印三行"HEHE"。在 main 函数中,我们调用 three_line 函数,从而实现打印三行"HEHE"的功能。
2、链式访问
- 所谓链式访问,就是将一个函数的返回值作为另一个函数的参数,像链条一样连接起来的访问形式。
- 例如:
#include <stdio.h>
#include <string.h>
int main()
{
// 计算字符串 "abcdef" 的长度,并将结果作为参数传递给 printf 函数进行打印
int len = strlen("abcdef");
printf("%d\n",len);
return 0;
}
- 如果直接将 strlen 的返回值作为 printf 函数的参数呢?
#include <stdio.h>
#include <string.h>
int main()
{
// 计算字符串 "abcdef" 的长度,并将结果作为参数传递给 printf 函数进行打印
printf("%d\n", strlen("abcdef"));
return 0;
}
- 上述代码中,使用了两个函数, strlen 函数和 printf 函数。先使用 len 将 strlen 函数计算出来的字符串长度保存起来,然后交给 printf 函数,作为 printf 函数的参数,最终实现打印。
3、经典例题
- 一段有趣的代码,猜一下执行结果是什么?
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
- 该代码的关键是,我们要知道printf函数的返回值是什么?
- 打开cplusplus.com网站对printf进行搜索,在Return Value一项中,我们可以看到:
- 翻译为:成功后,将返回写入的字符总数。
- 在这里,我们就知道printf返回的是打印在屏幕上的字符个数。
- 例⼦中,我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的-是第三个printf的返回值。
第三个printf打印43,在屏幕上打印2个字符,再返回2
第⼆个printf打印2,在屏幕上打印1个字符,再放回1
第⼀个printf打印1
所以屏幕上最终打印:4321
八、函数的声明和定义
1、函数声明
函数声明向编译器提供函数的名称、参数类型和返回类型等基本信息,它通常出现在程序的开头部分或头文件中,目的是让编译器在后续代码中知道如何使用该函数。
示例:
int add(int num1, int num2); // 这就是一个函数声明
2、函数定义
- 函数定义则完整地描述了函数的具体实现,包括函数体中的代码逻辑,用于真正实现函数的功能。
示例:
int add(int num1, int num2) { // 这里是函数定义
return num1 + num2;
}
3、单个文件
#include <stdio.h>
// 函数声明
int multiply(int a, int b);
int main() {
int result = multiply(5, 3);
printf("结果: %d\n", result);
return 0;
}
// 函数定义
int multiply(int a, int b) {
return a * b;
}
- 在这个例子中,先进行了 multiply 函数的声明,然后在 main 函数中调用它,最后给出了 multiply 函数的具体定义。这样编译器就能够正确地处理函数的调用和执行。
- 但如果我们只对函数进行定义而不声明,则会出现以下警告信息:
- 所以:
- 函数的调⽤⼀定要满足先声明后使⽤。
- 函数的定义也是⼀种特殊的声明,所以如果函数定义放在调⽤之前也是可以的。
4、多个文件
- 在实际工作中,因为代码量比较大,不会将所有的代码放在一个文件中,而是会根据功能的不同,拆分放在多个文件中。
- 一般而言,函数的声明、类型的声明放在头文件中(.h),函数的实现则是放在源文件中(.c),如下:
- 主函数
//使用自己制作的头文件时用" "
#include"add.h"
//使用库函数的头文件时用< >
#include<stdio.h>
int main()
{
int result = add(5, 3);
printf("%d\n", result);
return 0;
}
- add.c
//函数的定义
int add(int num1, int num2)
{
return num1 + num2;
}
- add.h
//函数的声明
int add(int num1, int num2) ;
- 运行结果为: