本文主要是介绍16 函数的基本概念、声明与调用、返回值、值传递、原型声明,文档注释,多文件编程(简易版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
1 函数基本概念
1.1 为什么需要函数
1.2 什么是函数
1.3 函数的作用
1.4 函数的分类
2 函数的声明与调用
2.1 结构说明
2.2 案例演示
2.3 函数不能嵌套声明
2.4 函数的调用
3 函数的返回值
3.1 无返回值类型
3.2 有返回值类型
3.3 有返回值但无 return 语句
3.4 返回类型不一致
3.4.1 如果转换是安全的
3.4.2 如果转换不安全或不可能
3.5 综合案例演示
4 函数的参数
4.1 形参与实参
4.2 参数传递 - 值传递
5 文档注释
6 函数原型
7 案例:通过函数原型实现多文程
7.1 搭建文件结构
7.2 编写源代码
7.3 统一编译源文件
1 函数基本概念
1.1 为什么需要函数
《街霸》游戏中,实现人物出拳、出脚或跳跃等动作都需要编写 50-80 行的代码,在每次出拳、出脚或跳跃的地方都需要重复地编写这 50-80 行代码,这样程序会变得很臃肿,可读性也非常差。为了解决代码重复编写的问题,可以将出拳、出脚或跳跃的代码提取出来放在一个 {} 中,并为这段代码起个名字,这样在每次的出拳、出脚或跳跃的地方通过这个名字来调用这个 {} 的代码就可以了。
提取出来的代码可以看作是程序中定义的一个函数,程序在需要出拳、出脚或跳跃时调用该函数即可。
1.2 什么是函数
函数是一种可重复使用的代码块,用于执行特定任务或操作。它允许我们将代码逻辑组织成独立的单元,从而提高代码的可读性、可维护性和重用性。
在 C 语言中,一个程序可以由一个或多个源文件组成(多文件编程),源文件的扩展名为 .c。每个源文件都是一个编译单位,并且可以包含多个函数。这些函数之间可以相互调用,因此函数是 C 程序的基本组成单位。
1.3 函数的作用
- 封装功能,将一个完整的功能封装成函数,提高代码的结构化和复用性。
- 代码模块化,将程序按照功能拆分成若干模块单元,有助于降低复杂度。
- 增强可维护性,如果需要修改某项功能,只需要调整对应的函数代码。
- 隔离细节,通过函数调用可以隐藏实现细节,只关心输入输出。
1.4 函数的分类
C 语言中,从使用的角度,函数可以分类两类。
1. 库函数,也称为标准函数,是由 C 系统提供的,用户不必自己定义,可直接使用它们,使用库函数,必须包含 #include 对应的头文件。
2. 自定义函数,解决具体需求而自己定义的函数,需先定义再使用。
2 函数的声明与调用
返回类型 函数名(参数列表)
{函数体语句1;函数体语句2;…………………………函数体语句n;return 返回值;
}
2.1 结构说明
函数名:函数被调用时使用的名字,函数名要符合标识符规范。
函数体:函数中所包含的代码块,用于实现函数的具体功能和操作。
参数列表(形参列表):用于接收调用函数时传递进来的值(实参)。
返回值:函数执行完毕后,从函数传回到调用点的值,返回值的类型要与函数名前面的返回类型对应,否者会发生未定义行为,如果没有返回值,返回类型可以写 void。
2.2 案例演示
下面的代码演示了如何在 C 语言中声明和定义函数:
#include <stdio.h>// 声明了一个名为 func 的函数
// 它没有参数也没有返回值,只是做简单的打印输出
void func()
{printf("hello func\n");
}// 声明了一个名为 minus 的函数
// 接受两个整数参数 m 和 n,并返回它们的差
int minus(int m, int n)
{return m - n;
}// 声明了一个名为 adds 的函数
// 接受两个 double 类型的参数 i 和 j,并返回它们的和
double add(double i, double j)
{double addRes = i + j;return addRes;// return i + j; 也可以直接返回数据
}// 声明了一个名为 max 的函数
// 接受两个整数参数 a 和 b,并返回较大的一个
int max(int a, int b)
{// 定义一个变量,存储结果int maxNum;maxNum = a > b ? a : b;return maxNum;
}// 主函数
int main()
{return 0;
}
2.3 函数不能嵌套声明
C 语言中,所有的函数都是互相独立的,它们之间不能嵌套定义。这意味着一个函数不能定义在另一个函数的内部。
- C 语言不允许在一个函数内部定义另一个函数。
- 每个函数都是独立存在的,并且可以通过函数名来调用。
- 函数可以互相调用,但不能嵌套定义。
//错误演示
int func1(int a,int b) //第 1 个函数的定义
{ ...int func2(int c,int d) //第 2 个函数的定义{ ...}...
}
有些编译器的扩展允许函数嵌套声明,但这不是 C 标准的一部分,代码的可移植性可能会受到影响,强烈不建议。
2.4 函数的调用
函数名后面加上圆括号即表示函数的调用,有参数的话,参数需要写在圆括号内。每当函数被调用一次,函数体内的语句都会被执行一遍。
#include <stdio.h>// 声明了一个名为 func 的函数
// 它没有参数也没有返回值(void类型),只是简单地打印输出
void func()
{printf("hello func\n"); // 打印字符串 "hello func" 到控制台
}// 声明了一个名为 minus 的函数
// 它接受两个整数参数 m 和 n,并返回它们的差
int minus(int m, int n)
{return m - n; // 返回两个参数的差
}// 声明了一个名为 add 的函数
// 它接受两个 double 类型的参数 i 和 j,并返回它们的和
double add(double i, double j)
{double addRes = i + j; // 计算两个参数的和,并存储在局部变量 addRes 中return addRes; // 返回和// 注意:return i + j; 也可以直接在这里返回数据,无需中间变量
}// 声明了一个名为 max 的函数
// 它接受两个整数参数 a 和 b,并返回较大的一个
int max(int a, int b)
{// 使用三元运算符来比较两个数并返回较大的一个int maxNum = a > b ? a : b;return maxNum; // 返回较大的数
}// 主函数,程序的入口点
int main()
{// 函数的调用示例// 1. 调用没有参数和返回值的函数func(); // 直接调用,打印 "hello func"func(); // 再次调用,再次打印 "hello func"// 2. 调用有参数和返回值的函数// 传递字面量作为参数printf("10-20的结果:%d\n", minus(10, 20)); // 打印 10 减去 20 的结果printf("20-10的结果:%d\n", minus(20, 10)); // 打印 20 减去 10 的结果// 传递变量作为参数double d1 = 10.0, d2 = 90.0; // 定义并初始化两个 double 类型的变量printf("10.0+90.0的结果:%.2f\n", add(d1, d2)); // 打印 d1 和 d2 的和,保留两位小数// 传递字面量作为参数printf("20.0+80.0的结果:%.2f\n", add(20.0, 80.0)); // 调用 max 函数来比较整数// 传递字面量作为参数printf("66和88之间较大的是:%d\n", max(66, 88)); // 打印 66 和 88 中较大的数printf("45和31之间较大的是:%d\n", max(45, 31)); // 注意更正了参数,以保持逻辑一致性// 演示对返回值的操作printf("可以操作返回来的数据:max(66, 88) + max(12,6) = %d \n", max(66, 88) + max(12, 6)); // 打印两个 max 函数返回值的和return 0; // 程序正常结束
}
输出结果如下所示:
3 函数的返回值
函数调用后数能得到一个确定的值,这就是函数的返回值,返回值常常是一个计算的结果,或是用来作为判断函数执行状态的标记。
3.1 无返回值类型
当函数无返回值或明确不需要返回值时,使用 void(即空类型)作为返回类型。
#include <stdio.h>// 无返回值类型的函数,使用 void(即空类型)表示。
void fun01()
{printf("调用了 fun01 函数\n");
}int main()
{// 调用无返回值的函数,仅执行其内部的打印操作fun01();return 0;
}
3.2 有返回值类型
明确指定返回值类型,如 int、float、char 等。在函数体内部使用 return 语句返回具体的值。
#include <stdio.h>// 有返回值类型的函数,返回 double 类型的值
double fun02()
{return 3.14;
}int main()
{ // 调用返回 double 类型值的函数,并打印其返回值printf("fun02() 返回的数据:%.2f \n", fun02());return 0;
}
3.3 有返回值但无 return 语句
如果函数声明的返回类型不是 void,说明函数需要返回数据,但函数体内部没有 return 语句,那么函数会返回一个不确定的值。
#include <stdio.h>// 返回值类型为 int,但函数中没有 return 语句
// 这将导致函数返回一个不确定的值(通常是未定义行为)
int fun03()
{10 + 20; // 这行代码仅计算了 30,但没有将其赋值给任何变量,也没有返回它// 由于缺少 return 语句,函数将返回一个不确定的值
}int main()
{// 调用返回不确定值的函数,打印结果可能是任何整数printf("fun03() 返回的数据(不确定值):%d \n", fun03());return 0;
}
3.4 返回类型不一致
如果函数的返回类型与 return 语句中表达式的类型不一致,编译器会尝试进行隐式类型转换。
3.4.1 如果转换是安全的
编译器会进行类型转换,将 return 语句中的值转换为函数声明的返回类型。这种转换可能涉及数据截断(例如,从 int 转换为 char)、符号扩展(例如,从 unsigned char 转换为 int)或浮点数的精度损失(例如,从 double 转换为 float)。
#include <stdio.h>// 返回值类型为 int,但 return 语句中的值是 double 类型
// 这种情况下,double 值会被隐式转换为 int 类型,导致精度损失
int fun04()
{return 20.89;
}int main()
{// 调用返回值类型不匹配的函数,打印结果为 20,因为 20.89 被隐式转换为整数printf("fun04() 返回的数据(精度损失):%d \n", fun04());return 0;
}
3.4.2 如果转换不安全或不可能
编译器会报错。例如,如果尝试从 void 类型的函数返回一个值(因为 void 类型表示“无类型”或“无返回值”),或者尝试返回一个指向局部变量的指针(因为局部变量在函数返回后会被销毁,导致悬垂指针),编译器可能会报错或给出警告。如下图所示:
3.5 综合案例演示
综合上述四种情况,我们通过下面的代码来进行综合的案例演示:
#include <stdio.h>// 无返回值类型的函数,使用 void(即空类型)表示。
void fun01()
{printf("调用了 fun01 函数\n");
}// 有返回值类型的函数,返回 double 类型的值
double fun02()
{return 3.14;
}// 返回值类型为 int,但函数中没有 return 语句
// 这将导致函数返回一个不确定的值(通常是未定义行为)
int fun03()
{10 + 20; // 这行代码仅计算了 30,但没有将其赋值给任何变量,也没有返回它// 由于缺少 return 语句,函数将返回一个不确定的值
}// 返回值类型为 int,但 return 语句中的值是 double 类型
// 这种情况下,double 值会被隐式转换为 int 类型,导致精度损失
int fun04()
{return 20.89;
}// 这个函数声明为 void 类型,意味着它不应该有返回值
// 如果尝试返回一个值(如已被注释的代码所示),编译器会发出警告或错误
void fun05()
{return 666; // 如果不注释这行代码,这会导致编译错误或警告,因为 void 函数不能返回值
}int main()
{// 调用无返回值的函数,仅执行其内部的打印操作fun01();// 调用返回 double 类型值的函数,并打印其返回值printf("fun02() 返回的数据:%.2f \n", fun02());// 调用返回不确定值的函数,打印结果可能是任何整数printf("fun03() 返回的数据(不确定值):%d \n", fun03());// 调用返回值类型不匹配的函数,打印结果为 20,因为 20.89 被隐式转换为整数printf("fun04() 返回的数据(精度损失):%d \n", fun04());return 0;
}
输出结果如下所示:
提示:为了避免潜在的问题和保持代码的可读性,建议总是确保函数的返回类型与 return 语句中表达式的类型一致,或者至少确保它们之间的转换是明确且安全的。如果需要进行类型转换,最好显式地进行(使用类型转换运算符,如 (int) ),这样可以使代码的意图更加清晰。
现代 C 编译器通常会在检测到可能的类型转换问题时发出警告或错误。这些警告和错误可以帮助开发者识别并修复潜在的问题。因此,建议开启编译器的所有警告选项,并仔细审查编译器输出的任何警告信息。
4 函数的参数
函数的参数分为形参与实参。
4.1 形参与实参
在定义函数时,函数名后面括号 () 中声明的变量称为形式参数,简称形参。
在调用函数时,函数名后面括号 () 中的使用的常量、变量、表达式称为实际参数,简称实参。
注意:实参的数量要与形参的数量一致,否则会报错。
4.2 参数传递 - 值传递
在 C 语言中,当我们调用一个函数时,实参(实际参数)用于初始化形参(形式参数)。这个过程通常被称为 “参数传递”。
当调用一个函数时,实参的值会被复制给形参,形参就像是一个临时变量,用于存储实参的值。因此,可以认为实参 “初始化” 了形参,但这并不是一个标准术语,而是描述这一过程的一种说法。
C 语言本身不直接支持引用传递,但可以通过传递实参的地址来间接实现(后续学习)。此时,形参通常是指针,指向实参的地址。
#include <stdio.h>/*** 函数功能:计算两个整数的和* @param x 第一个整数(形参)* @param y 第二个整数(形参)* @return 返回 x 和 y 的和*/
int func(int x, int y)
{// 返回两个整数的和return x + y;
}int main()
{// 调用 func 函数,实参为 3 和 5,用于计算它们的和int sum = func(3, 5);printf("%d \n", sum); // 输出:8// 如果实参数量与形参不一致时,编译器会报错func(100, 299, 300); // 错误:func 函数只接受两个参数func(100); // 错误:func 函数需要两个参数return 0;
}
如果实参数量与形参数量不一致,编译器会报错,如下所示:
5 文档注释
在 C 语言中,传统的注释有两种形式:
- 单行注释:使用 // 开头。
- 多行注释:使用 /* 开始并以 */ 结束。
然而,这些传统的注释方式并不能直接生成可读性高的文档。为了生成易于阅读和理解的文档,程序员们常常使用一种特殊的注释格式——文档注释。虽然 C 语言本身并不支持特定的文档注释语法,但一些工具可以解析特定格式的注释来生成文档。
VS Code 中文档注释的快捷键是 /** 。
/*** @brief 计算两个整数的和* * @param x 第一个整数* @param y 第二个整数* * @return 返回 x 和 y 的和*/
int func(int x, int y)
{// 返回两个整数的和return x + y;
}
- @brief:简要描述函数的功能。
- @param:描述每个参数的作用。
- @return:描述函数返回值的意义。
6 函数原型
在 C 语言中,默认情况下,函数必须先声明后使用。因为在调用一个函数之前,编译器需要了解函数的返回类型和参数列表等基本信息。鉴于程序执行通常是从 main() 函数开始的,我们习惯于将所有函数的声明放置在 main() 函数之前。
如果想将函数的声明放在 main() 函数之后,可以通过在程序的开头部分给出函数的原型。函数原型包含了必要的信息(如返回类型、参数类型及数量),但不包括具体的实现逻辑。这样一来,即使函数的实际定义位于程序的较后位置,编译器也能正确地进行处理。
#include <stdio.h>// 函数原型声明
int twice1(int num1, int num2); // 分号;是必需的
int twice2(int, int, int); // 形参的名称可以省略掉// 主函数
int main(void)
{int result1, result2;// 调用函数 twice1 并打印结果result1 = twice1(10, 5); // 30printf("twice1(10, 5) = %d\n", result1);// 调用函数 twice2 并打印结果result2 = twice2(10, 5, 2); // 34printf("twice2(10, 5, 2) = %d\n", result2);return 0;
}// 函数 twice1 的定义
/*** @brief 这个函数接收两个整数作为参数,并返回它们乘以 2 的结果** @param num1* @param num2* @return int*/
int twice1(int num1, int num2)
{return (num1 + num2) * 2;
}// 函数 twice2 的定义
/*** @brief 这个函数接收三个整数作为参数,并返回它们乘以 2 的结果** @param num1* @param num2* @param num3* @return int*/
int twice2(int num1, int num2, int num3)
{return (num1 + num2 + num3) * 2;
}
7 案例:通过函数原型实现多文程
学习完函数原型之后,我们可以利用这一功能特性来实现简易版的多文件编程。具体步骤如下:
7.1 搭建文件结构
首先创建一个名为 math 的文件夹,在 math 里面创建五个源文件,分别为 main.c、add.c、sub.c、mul.c 和 div.c。
- main.c:包含 main 函数和所有函数的原型声明。
- add.c:只包含加法函数的实现。
- sub.c:只包含减法函数的实现。
- mul.c:只包含乘法函数的实现。
- div.c:只包含除法函数的实现,并注意处理除数为 0 的情况。
7.2 编写源代码
main.c 中代码内容如下所示:
#include <stdio.h>// 函数原型声明
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b); int main()
{printf("5 + 3 = %d\n", add(5, 3));printf("5 - 3 = %d\n", sub(5, 3));printf("5 * 3 = %d\n", mul(5, 3));printf("5 / 3 = %d\n", div(5, 3));return 0;
}
add.c 中代码内容如下所示:
// add.c
// 只包含加法函数的实现int add(int a, int b)
{return a + b;
}
sub.c 中代码内容如下所示:
// sub.c
// 只包含减法函数的实现 int sub(int a, int b) { return a - b;
}
mul.c 中代码内容如下所示:
// mul.c
// 只包含乘法函数的实现 int mul(int a, int b) { return a * b;
}
div.c 中代码内容如下所示:
// div.c
// 只包含除法函数的实现,注意处理除数为 0 的情况int div(int a, int b)
{if (b == 0){// 这里只是简单地返回 0,实际中可能需要更复杂的错误处理// 这通常不是一个好的做法,因为它隐藏了错误return 0; }return a / b;
}
7.3 统一编译源文件
在 VS Code 中,如果想编译多个 C 文件,通常会使用 tasks.json 文件来配置一个编译任务。这个任务会告诉 VS Code 如何调用编译器(如 gcc 或 clang )来编译你的代码。
在这里,我们的目标是简单地实现一个多文件编程的 C 项目,而不想通过 VS Code 的复杂配置(如修改 tasks.json)来达成。我们可以直接利用命令行(cmd)这一更为基础且灵活的工具来编译我们的多个 C 源文件,具体操作如下图所示:
这篇关于16 函数的基本概念、声明与调用、返回值、值传递、原型声明,文档注释,多文件编程(简易版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!