函数的概念

C语言中的函数就是一个完成某项特定的任务的一段代码,有时候被翻译成子程

序。这段代码是有特殊的写法和调用方法的。使用函数可以实现代码复用,提升

开发效率。

在C语言中一般会遇到两种函数:

  • 库函数
  • 自定义函数

库函数

标准库和头文件

C语言并不提供库函数。

ANSI C 规定了一些常用的函数标准,被称为标准库。

不同的编译器厂商根据标准库给出一系列函数实现。这些函数被称为库函数。

比如 printf() scanf()

各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,都

在不同的头文件中进行了声明。

头文件包含

库函数是在标准库中对应的头文件中声明的,所以库函数的使用必须包含对应的

头文件。

自定义函数

语法形式

1
2
3
4
5
ret_type fun_name(形式参数)
{

}

  • ret_type 是函数返回值的类型,酌情设置,没有返回值就是 void 。

  • fun_name 是函数名,方便使用。

  • 括号中放的是形式参数,不需要参数也可以用 void 。 如果有参数需要指

    明参数类型和参数个数。

  • {}中是函数体,完成函数的计算过程。

自定义函数示例

假如现在自定义一个简单的函数 Add() , 传入两个整型参数,计算出这两个整型参

数的和并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int Add(int a, int b)
{
// 一个简单的加法函数,它接受两个整数参数,并返回它们的和。
return a + b;
}

int main()
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
int sum = Add(x, y);
printf("%d 和 %d 的和是: %d\n", x, y, sum);
return 0;
}

函数的参数部分需要交代清楚:参数个数,参数类型,形参名称。

形参和实参

在函数使用过程中,把函数的参数分为实参和形参。


实参

在上述代码中,调用 Add 函数时将变量 x 和 y 的值传过去了,x 和 y 就是实参。

实参就是传递给函数的参数。


形参

在上述代码中, Add 函数定义时括号中的参数 a 和 b 就是形参。

不去调用 Add 的时候, a 和 b 只是形式上存在,并没有向内存申请空间,没有

实际存在,所以称作形参。


实参和形参的关系

通过监视来看一下实参和形参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int Add(int a, int b)
{
// 一个简单的加法函数,它接受两个整数参数,并返回它们的和。
return a + b;
}

int main()
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
int sum = Add(x, y);
printf("%d 和 %d 的和是: %d\n", x, y, sum);
return 0;
}

可以看到,形参取得了实参的值,但是形参和实参的地址不同,可以理解成形参

是实参的临时拷贝。

实参传值,对形参的操作不会影响实参。

实参传地址,对形参的操作会影响实参。

具体的会在后文介绍。

return 语句

在函数设计中经常会出现 return 语句。

  • return 后可以是值也可以是表达式。表达式会先执行再返回。

  • 返回值与函数返回类型不一致,系统会自动将返回值隐式转换为函数返回

类型。

  • return 语句执行后,函数将立刻结束。

  • 如果函数中存在分支语句,则需保证任意情况下都有 return 返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void test1(void)
{
// 这是一个简单的测试函数,演示了 return 会结束函数。
return;
printf("666\n");
}

int test2(void)
{
// 这是一个简单的测试函数,演示了 return 返回表达式会先计算表达式。
return 1 + 1;
}

int test3(int a, int b)
{
// 这是一个简单的测试函数,演示了分支语句下 return 可以返回不同的值。
if (a > b)
{
return a;
}
else
{
return b;
}
}


int main()
{
test1();
int result2 = test2();
printf("test2 的结果是: %d\n", result2);
int max = test3(10, 20);
printf("test3 的结果是: %d\n", max);
return 0;
}

数组做函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void set_arr(int arr1[10],int sz)
{
// 这是一个简单的测试函数,演示了数组作为参数传递,并设置数组元素的值。
for (int i = 0;i < sz;i++)
{
arr1[i] = 0;
}
}

void print_arr(int arr2[10], int sz)
{
// 这是一个简单的测试函数,演示了数组作为参数传递,并打印数组元素的值。
for (int i = 0; i < sz; i++)
{
printf("%d ", arr2[i]);
}
printf("\n");
}

int main()
{
int arr[10] = {0};
int sz1 = sizeof(arr) / sizeof(arr[0]);
set_arr(arr, sz1);
print_arr(arr, sz1);
return 0;
}

  • 函数的形参个数和实参个数要匹配

  • 实参是数组,形参也可以是数组形式

  • 形参如果是一维数组,数组大小可以不写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    void print_arr_2(int arr2[], int sz)
    {
    // 如果参数是一维数组,数组大小可以省略,编译器会根据传入的数组自动推断出数组大小。
    // 这是一个简单的测试函数,演示了数组作为参数传递,并打印数组元素的值。
    for (int i = 0; i < sz; i++)
    {
    printf("%d ", arr2[i]);
    }
    printf("\n");
    }

    int main()
    {
    int arr[10] = { 0 };
    int sz1 = sizeof(arr) / sizeof(arr[0]);
    print_arr_2(arr, sz1);
    return 0;
    }

  • 形参如果是二位数组,行可忽略列不可忽略

    1
    2
    3
    4
    5
    void print_arr_3(int arr2[][], int sz)
    {
    // 形参如果是二位数组,行可忽略列不可忽略

    }

  • 数组传参,形参不会创建新的数组

  • 形参操作的数组和实参的数组实际上是同一个数组

    通过监视窗口可以看到实际上用的就是同一个数组。
    这里因为传数组名是

    传的地址,所以对形参的操作影响到了实参。

函数的嵌套调用

就是指函数之间的互相调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void test4(void)
{
// 这是一个简单的测试函数,用来被test5函数调用。
printf("我是 test4 函数\n");
return;
}

void test5(void)
{
// 这是一个简单的测试函数,用来演示函数嵌套调用。
printf("我是 test5 函数\n");
test4();
return;
}

int main()
{
test5();
return 0;
}

注:函数不能嵌套定义

函数的链式访问

指将一个函数的返回值作为另外一个函数的参数传入。

1
2
3
4
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("%d\n", Add(a,b));

函数的声明和定义

函数的调用一定要满足先声明后使用。

1
2
3
4
5
6
7
8
9
10
11
nt main()
{
exp();
return 0;
}

void exp(void)
{
// 演示函数要先声明后才能使用,否则会出现编译错误。
printf("666\n");
}

如果非要把函数的定义放在主程序后,那就在主程序执行前进行声明。

1
2
3
4
5
6
7
8
9
10
11
12
void exp(void); // 函数声明
int main()
{
exp();
return 0;
}

void exp(void)
{
// 演示函数要先声明后才能使用,否则会出现编译错误。
printf("666\n");
}

多个文件

当代码比较多时就不宜将代码放在同一个文件中,此时可以将函数的声明和定义

放在头文件中。再在源文件中包含这个头文件。

1
2
3
4
5
6
7
#pragma once
// sub.h
int Sub(int a, int b)
{
// 一个简单的减法函数,它接受两个整数参数,并返回它们的差。
return a - b;
}
1
2
3
4
5
6
7
8
9
10
11
#include "sub.h"
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int num = Sub(a, b);
printf("%d 和 %d 的差是: %d\n", a, b, num);
return 0;
}

static 和 extern

static 和 extern 都是C语言中的关键字。

static 可以用来:

  • 修饰局部变量
  • 修饰全局变量
  • 修饰函数

extern 是用来声明外部符号的

作用域

  • 局部变量的作用域是变量所在的局部范围
  • 全局变量的作用域是整个工程

生命周期

  • 局部变量的生命周期是进入作用域变量创建,出作用域生命周期结束。
  • 全局变量的生命周期是整个程序的生命周期。

static 修饰局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void exp2()
{
int j = 0;
j++;
printf("%d ", j);
}
int main()
{
int j = 0;
for (j = 0; j < 5; j++)
{
exp2();
}
return 0;
}


这段代码中局部变量j是每次进入test函数先创建变量(生命周期开始)并赋值为

0,然后
++,再打印,出函数的时候变量生命周期将要结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void exp3()
{
static int j = 0; // 静态局部变量,初始值为0
j++;
printf("%d ", j);
}

int main()
{
// 用 static 修饰的局部变量在函数调用结束后不会被销毁,而是一直存在,直到程序结束。
int j = 0;
for (j = 0; j < 5;j++)
{
exp3();
}
return 0;
}


这段代码中 j 创建好后,出函数的时候是
不会销毁的,重新进入函数也就不会重

新创建变量,直接上次累积的数值继续计算。

static修饰局部变量改变了变量的生命周期。

static 修饰全局变量

extern 来声明外部符号的,如果一个全局的符号在A文件中定义的,在B文件中想

使用,就可以使用 extern 进行声明然后使用。

1
2
3
4
5
6
7
8
9
#pragma once
// sub.h
int g_num1 = 10;

int Sub(int a, int b)
{
// 一个简单的减法函数,它接受两个整数参数,并返回它们的差。
return a - b;
}
1
2
3
4
5
6
extern int g_num1; // 声明外部全局变量
int main()
{
printf("g_num1 = %d\n", g_num1); // 输出全局变量的值
return 0;
}

一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其

他源文件内使用。

1
2
3
4
5
6
7
8
9
#pragma once
// sub.h
int g_num1 = 10;
static int g_num2 = 20; // 静态全局变量,作用域仅限于当前文件
int Sub(int a, int b)
{
// 一个简单的减法函数,它接受两个整数参数,并返回它们的差。
return a - b;
}
1
2
3
4
5
6
7
extern int g_num1; // 声明外部全局变量
extern int g_num2; // 声明外部静态全局变量
int main()
{
printf("g_num2 = %d\n", g_num2);
return 0;
}

全局变量默认具有外部链接属性,但在被 static 修饰后,就变为了内部链接属

性,只能在变量本身所在的源文件内使用。

static 修饰函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#pragma once
// sub.h
#include <stdio.h>
int g_num1 = 10;
static int g_num2 = 20; // 静态全局变量,作用域仅限于当前文件
int Sub(int a, int b)
{
// 一个简单的减法函数,它接受两个整数参数,并返回它们的差。
return a - b;
}

static void ceshi(void)
{
printf("666\n"); // 一个静态函数,作用域仅限于当前文件,无法被其他文件访问。
}
1
2
3
4
5
6
extern void ceshi(void); // 声明外部静态函数
int main()
{
ceshi(); // 调用外部静态函数
return 0;
}

用 static 修饰函数与修饰全局变量是类似的。只能在本源文件中使用。

函数默认具有外部链接属性,但在被 static 修饰后,就变为了内部链接属

性,只能在函数本身所在的源文件内使用。