【C语言】:函数深度解析
函数(Function)是C语言程序的基本组成单元,它是一段用于完成特定任务的、可重用的代码块。通过使用函数,我们可以实现程序的模块化,提高代码的可读性、可维护性和复用性。
1. 函数的概念
在程序开发中,我们常常需要重复执行某些操作。函数允许我们将这些操作封装起来,并为其命名。当需要执行这些操作时,只需通过函数名来调用 (call)它,而无需重复编写代码。每个C语言程序都从一个特殊的函数 main 开始执行,main 函数是程序的入口。
2. 库函数
2.1 标准库和头文件
C语言本身只提供了非常基础的语法结构。为了方便开发,C语言标准规定了一组常用的函数,这些函数集合被称为C标准库。标准库提供了诸如输入输出、字符串处理、数学计算等丰富的功能。
这些库函数根据功能被划分在不同的头文件 (Header File,通常以 .h 结尾)中。头文件中包含了函数的声明、宏定义、类型定义等信息。
库函数相关头文件:https://zh.cppreference.com/w/c/header
2.2 库函数的使用方法
库函数的学习和查看⼯具很多,⽐如:
C/C++官方的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
要使用一个库函数,通常需要以下步骤:
2.2.1 功能
了解函数的功能。例如,sqrt 函数用于计算一个数的平方根。
2.2.2 头文件包含
使用 #include 预处理指令将包含该函数声明的头文件引入到我们的源文件中。sqrt 函数的声明位于 math.h 头文件中。
c
#include <math.h> // 使用尖括号<>表示在系统标准路径下查找头文件
2.2.3 实践
根据函数的原型(参数类型、个数和返回值类型)来正确调用函数。
c
#include <stdio.h>
#include <math.h>
int main() {
double result = sqrt(16.0); // 调用sqrt函数
printf("The square root of 16.0 is %f\n", result);
return 0;
}
2.2.4 库函数文档的一般格式
学习和查询库函数时,可以参考官方文档(如cppreference.com)或使用man命令(在Linux/macOS下)。文档通常会提供以下信息:
- 函数原型 (Prototype):定义了函数的返回类型、名称和参数列表。
- 功能描述 (Description):解释函数的作用。
- 参数说明 (Parameters):详细解释每个参数的含义和要求。
- 返回值 (Return Value):说明函数的返回值代表什么意义。
- 所需头文件 (Header) :指明需要
#include哪个头文件。
3. 自定义函数
除了使用库函数,我们更多时候需要根据自己的需求创建函数,这称为自定义函数。
3.1 函数的语法形式
一个完整的函数定义包括返回类型 、函数名 、参数列表 和函数体。
c
返回类型 函数名(参数列表)
{
// 函数体:包含声明和语句
return 返回值; // 如果返回类型不是void,则需要
}
- 返回类型 (Return Type) :函数执行完毕后返回给调用者的数据的类型。如果函数不返回任何值,则使用
void。 - 函数名 (Function Name):函数的唯一标识符。
- 参数列表 (Parameter List) :函数接收的输入数据。每个参数都包含类型和名称。如果函数不接收任何参数,参数列表应为
void。 - 函数体 (Function Body) :被
{}包裹的代码块,是函数功能的具体实现。
3.2 函数的举例
c
// 定义一个计算两个整数之和的函数
int add(int x, int y) // 返回类型是int, 函数名是add, 参数是两个int
{
int sum = x + y;
return sum; // 返回计算结果
}
// 定义一个不接收参数,不返回值的函数
void print_hello(void)
{
printf("Hello, World!\n");
}
4. 形参和实参
4.1 实参
实际参数 (Argument) ,简称实参,是在调用函数时,传递给函数的实际值。实参可以是常量、变量、表达式,甚至另一个函数的返回值。
c
int main() {
int a = 10, b = 20;
int result = add(a, b); // a 和 b 是实参
add(5, 8); // 5 和 8 是实参
return 0;
}
4.2 形参
形式参数 (Parameter) ,简称形参,是在定义函数时,在函数名后的括号内声明的变量。形参是函数内部的局部变量,它们作为接收实参值的占位符。
c
int add(int x, int y) // x 和 y 是形参
{
// ...
}
4.3 实参和形参的关系
核心机制:值传递 (Pass by Value)
在C语言中,函数调用时发生的是值传递。这意味着:
- 为形参分配独立的内存空间。
- 将实参的值复制一份,然后赋给对应的形参。
- 在函数内部对形参的任何修改,都不会影响到函数外部的实参。
形参是实参的一份临时拷贝。
5. return 语句
return 语句用于终止当前函数的执行,并将控制权返回给调用者。
- 语法 :
return 表达式;或return; return后的表达式的值将作为函数的返回值。该值的类型必须与函数声明的返回类型兼容。- 对于返回类型为
void的函数,可以使用不带表达式的return;来提前结束函数,或者省略return语句。 - 一个函数可以包含多个
return语句(例如在不同的if-else分支中),但只要执行到任何一个return,函数就会立即结束。
6. 数组做函数参数
当将数组传递给函数时,实际上传递的并不是 整个数组的拷贝,而是数组首元素的地址。
c
void print_array(int arr[], int size) // arr[]实际上是一个指针
{
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
语法细节:
- 函数形参
int arr[]的写法本质上是一个语法糖,它等价于int *arr。编译器会将其视为一个指向int类型的指针。 - 正因为传递的是地址,所以在函数内部对数组元素
arr[i]的修改,会直接影响到原始的实参数组。 - 由于传递的只是地址,函数本身无法知道数组的大小。因此,在传递数组时,必须额外传递一个表示数组大小的参数。
7. 嵌套调用和链式访问
7.1 嵌套调用
一个函数可以调用另一个函数,这称为函数的嵌套调用。
c
int square(int n) { return n * n; }
int sum_of_squares(int x, int y) {
return square(x) + square(y); // 嵌套调用了square函数
}
7.2 链式访问
链式访问指的是将一个函数的返回值作为另一个函数的参数直接使用。
c
printf("%d\n", add(5, 10)); // add(5, 10)的返回值直接作为printf的参数
8. 函数的声明和定义
- 函数定义 (Definition):指提供了函数体的完整实现。
- 函数声明 (Declaration) :也称为函数原型,它只告诉编译器函数的名称、返回类型和参数列表,而不提供函数体。声明的作用是让编译器在遇到函数调用时,能检查调用的合法性,即使函数的完整定义在后面或别的-文件中。
8.1 单个文件
在一个源文件中,如果函数定义在函数调用之后,那么在调用之前必须有该函数的声明。
c
#include <stdio.h>
int add(int, int); // 函数声明
int main() {
printf("%d\n", add(3, 4)); // 合法,因为有声明
return 0;
}
int add(int x, int y) { // 函数定义
return x + y;
}
8.2 多个文件
在大型项目中,代码通常被分散在多个 .c 文件中。如果一个文件(如 main.c)需要调用另一个文件(如 utils.c)中定义的函数,就必须在 main.c 中包含该函数的声明。通常的做法是:
-
创建一个头文件(如
utils.h),在其中放置函数的声明。 -
在
utils.c中#include "utils.h"并提供函数定义。 -
在
main.c中#include "utils.h"来获取函数的声明,然后就可以调用了。使用
""而不是<>来包含自定义头文件,表示优先在当前目录下查找。
8.3 static 和 extern
这两个关键字用于控制变量和函数的链接属性 和存储期。
8.3.1 static 修饰局部变量
- 改变存储期 :
static将局部变量的存储位置从栈区移到静态存储区。这使得变量的生命周期从"函数执行期间"延长到"整个程序运行期间"。 - 作用域不变:该变量的作用域仍然仅限于其所在的代码块。
- 保留值 :函数调用结束后,
static局部变量的值不会被销毁,下次进入该函数时会保留上次的值。 - 初始化:只在第一次进入函数时被初始化一次。
8.3.2 static 修饰全局变量
- 改变链接属性 :
static将全局变量的外部链接 属性(默认)改为内部链接 。这意味着该全局变量只能在它被定义的那个源文件(.c文件)内部访问,其他源文件即使使用extern也无法访问它。这提供了一种数据封装和隔离的机制。
8.3.3 static 修饰函数
- 改变链接属性 :与修饰全局变量类似,
static将函数的外部链接 属性(默认)改为内部链接。这使得该函数成为一个"内部函数",只能被它所在的源文件中的其他函数调用。这有助于避免在大型项目中出现函数命名冲突。
extern 关键字则用于声明 一个具有外部链接属性的变量或函数。例如,在 a.c 中定义了 int global_var;,在 b.c 中可以通过 extern int global_var; 来声明并使用它。