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.2.1 正常情况

3.2.2 无 return 语句

3.2.3 返回类型不一致

3.3 案例演示

4 函数的参数

4.1 形参与实参

4.2 参数传递 -> 值传递 

5 文档注释

6 函数的原型声明

7 多文件编程

7.1 搭建项目结构

7.2 编写源代码

7.3 一次性编译多个源文件


1 函数的基本概念

1.1 为什么需要函数

        在《街霸》游戏中,实现人物的出拳、出脚或跳跃等动作通常需要编写 50-80 行的代码。如果在每次需要这些动作时都重复编写相同的代码,不仅会使程序变得臃肿,还会影响代码的可读性和维护性。为了解决这一问题,可以将出拳、出脚或跳跃相关的代码提取出来,放在一个 {} 中,并为这段代码命名,这样就可以在需要执行这些动作的地方通过名称来调用这段代码。

        提取出来的这部分代码可以视为程序中的一个函数。每当游戏需要执行出拳、出脚或跳跃的动作时,只需调用相应的函数即可。这种方法不仅减少了代码重复,还提高了程序的整洁度和可维护性。

1.2 什么是函数

        函数是一种可重复使用的代码块,用于执行特定任务或操作。它允许我们将代码逻辑组织成独立的单元,从而提高代码的可读性、可维护性和重用性。

        在 C 语言中,一个程序可以由一个或多个源文件组成(多文件编程),源文件的扩展名为 .c。每个源文件都是一个编译单位,并且可以包含多个函数,这些函数之间可以相互调用,因此函数是 C 程序的基本组成单位

1.3 函数的作用

  • 封装功能,将一个完整的功能封装成函数,提高代码的结构化和复用性。
  • 代码模块化,将程序按照功能拆分成若干模块单元,有助于降低复杂度。
  • 增强可维护性,如果需要修改某项功能,只需要调整对应的函数代码。
  • 隔离细节,通过函数调用可以隐藏实现细节,只关心输入输出。

1.4 函数的分类

        C 语言中,从使用的角度,函数可以分类两类。

        1. 库函数,也称为标准函数,是由 C 系统提供的,用户不必自己定义,可直接使用它们,使用库函数,必须包含 #include 对应的头文件

        2. 自定义函数,解决具体需求而自己定义的函数,需先定义再使用


2 函数的定义与调用

2.1 函数的定义

        函数定义提供了函数的实际实现代码,即函数体。它包含了函数如何完成其任务的所有指令。函数定义包括函数的返回类型、函数名、参数列表(参数的类型和名称)以及函数体(包含执行函数任务的代码)。在定义函数时,编译器或解释器会生成相应的机器代码或字节码,以便在函数调用时执行。

返回类型 函数名(参数列表)
{
    函数体语句1;
    函数体语句2;
    …………………………
    函数体语句n;

    return 返回值;
}

        结构说明:

        函数名:函数被调用时使用的名字,函数名要符合标识符规范

        函数体:函数中所包含的代码块,用于实现函数的具体功能和操作

        参数列表(形参列表):用于接收调用函数时传递进来的值(实参)

        返回值:函数执行完毕后,从函数传回到调用点的值,返回值的类型要与函数名前面的返回类型对应,否者会发生未定义行为如果没有返回值,返回类型可以写 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");
}

// 定义了一个名为 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; // 返回较大的数
    // return a > b ? a : b; 也可以直接返回结果
}

// 主函数,程序的入口点
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)); // 注意更正了参数,以保持逻辑一致性

    // 演示对返回值的操作
    // 打印两个 max 函数返回值的和
    printf("可以操作返回来的数据:max(66, 88) + max(12,6) = %d \n", max(66, 88) + max(12, 6)); 

    return 0; // 程序正常结束
}

        输出结果如下所示:


3 函数的返回值

        函数调用后可以返回一个确定的值,这个值称为函数的返回值。返回值通常代表某种计算结果,或者用于指示函数执行的状态。

3.1 无返回值类型

        当函数没有返回值或明确不需要返回值时,可以使用 void 作为其返回类型。

#include <stdio.h>

// 无返回值类型的函数,使用 void(即空类型)表示。
void fun01()
{
    printf("调用了 fun01 函数\n");
}

int main()
{
    // 调用无返回值的函数,仅执行其内部的打印操作
    fun01();

    return 0;
}

         输出结果如下所示:

3.2 有返回值类型

3.2.1 正常情况

        明确指定函数的返回值类型,例如 int、float、char 等,并在函数体内部使用 return 语句来返回具体的值。

#include <stdio.h>

// 有返回值类型的函数,返回 double 类型的值
double fun02()
{
    return 3.1415926;
}

int main()
{
    // 调用返回 double 类型值的函数,并打印其返回值
    printf("fun02() 返回的数据:%.2f \n", fun02()); // 3.14

    return 0;
}

        输出结果如下所示:  

3.2.2 无 return 语句

        如果函数定义的返回类型不是 void,这意味着函数应该返回数据。但如果函数体内部没有 return 语句,函数将会返回一个不确定的值。

#include <stdio.h>

// 返回值类型为 int,但函数中没有 return 语句
// 这将导致函数返回一个不确定的值(通常是未定义行为)
int fun03()
{
    10 + 20; // 这行代码仅计算了 30,但没有将其赋值给任何变量,也没有返回它
    // 由于缺少 return 语句,函数将返回一个不确定的值
}

int main()
{
    // 调用返回不确定值的函数,打印结果可能是任何整数
    printf("fun03() 返回的数据(不确定值):%d \n", fun03());

    return 0;
}

        输出结果如下所示:

3.2.3 返回类型不一致

        如果函数的返回类型与 return 语句中表达式的类型不一致,编译器会尝试进行隐式类型转换

        如果转换是安全的:编译器会进行类型转换,将 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;
}

        输出结果如下所示:

         如果转换不安全或不可能:编译器会警告或报错。例如,如果尝试从 void 类型的函数返回一个值(因为 void 类型表示无返回值),或者尝试返回一个指向局部变量的指针(因为局部变量在函数返回后会被销毁,导致悬垂指针),编译器可能会给出警告或报错。如下图所示:

3.3 案例演示

        综合上述返回值的四种情况,我们通过下面的代码来进行综合的案例演示:

#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() 函数之后,可以通过在程序的开头部分给出函数的原型(即函数声明)。函数原型包含了必要的信息(如返回类型、函数的名称、参数列表[形参可以省略,但是形参的数据类型不可以省略]),但不包括具体的实现逻辑。这样一来,即使函数的实际定义位于程序的较后位置,编译器也能正确地进行处理。例如,如果有一个函数 int add(int, int); 这就是一个函数原型,它告诉编译器这个函数名为 add,返回值类型为 int 并且接受两个 int 类型的参数。

        函数原型的主要用途:

  • 告诉编译器函数的接口信息。
  • 在函数调用前进行声明,以确保在实际调用时编译器能够正确识别该函数。
#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); // 30
    printf("twice1(10, 5) = %d\n", result1);

    // 调用函数 twice2 并打印结果
    result2 = twice2(10, 5, 2); // 34
    printf("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 源文件,具体操作如下图所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thanks_ks

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值