16 函数的基本概念、声明与调用、返回值、值传递、原型声明,文档注释,多文件编程(简易版)

本文主要是介绍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 函数的基本概念、声明与调用、返回值、值传递、原型声明,文档注释,多文件编程(简易版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1104960

相关文章

idea中创建新类时自动添加注释的实现

《idea中创建新类时自动添加注释的实现》在每次使用idea创建一个新类时,过了一段时间发现看不懂这个类是用来干嘛的,为了解决这个问题,我们可以设置在创建一个新类时自动添加注释,帮助我们理解这个类的用... 目录前言:详细操作:步骤一:点击上方的 文件(File),点击&nbmyHIgsp;设置(Setti

在C#中调用Python代码的两种实现方式

《在C#中调用Python代码的两种实现方式》:本文主要介绍在C#中调用Python代码的两种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#调用python代码的方式1. 使用 Python.NET2. 使用外部进程调用 Python 脚本总结C#调

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

SpringCloud之LoadBalancer负载均衡服务调用过程

《SpringCloud之LoadBalancer负载均衡服务调用过程》:本文主要介绍SpringCloud之LoadBalancer负载均衡服务调用过程,具有很好的参考价值,希望对大家有所帮助,... 目录前言一、LoadBalancer是什么?二、使用步骤1、启动consul2、客户端加入依赖3、以服务

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

kotlin的函数forEach示例详解

《kotlin的函数forEach示例详解》在Kotlin中,forEach是一个高阶函数,用于遍历集合中的每个元素并对其执行指定的操作,它的核心特点是简洁、函数式,适用于需要遍历集合且无需返回值的场... 目录一、基本用法1️⃣ 遍历集合2️⃣ 遍历数组3️⃣ 遍历 Map二、与 for 循环的区别三、高

Vue 调用摄像头扫描条码功能实现代码

《Vue调用摄像头扫描条码功能实现代码》本文介绍了如何使用Vue.js和jsQR库来实现调用摄像头并扫描条码的功能,通过安装依赖、获取摄像头视频流、解析条码等步骤,实现了从开始扫描到停止扫描的完整流... 目录实现步骤:代码实现1. 安装依赖2. vue 页面代码功能说明注意事项以下是一个基于 Vue.js

Python实现合并与拆分多个PDF文档中的指定页

《Python实现合并与拆分多个PDF文档中的指定页》这篇文章主要为大家详细介绍了如何使用Python实现将多个PDF文档中的指定页合并生成新的PDF以及拆分PDF,感兴趣的小伙伴可以参考一下... 安装所需要的库pip install PyPDF2 -i https://pypi.tuna.tsingh