第6章 函数(2026.5正在更新)
运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主
0.章节引言
-
1.随着程序的膨胀和复杂化,将所有代码“堆积”到
main()函数中不利于代码维护,把程序分解成不同的“功能段”会使工作简化、程序明晰,这些“功能段”即函数 -
2.函数由实现一定任务的语句序列组成,是实现特定功能的一个模块,是构成 C 程序的基本单元。它可以减轻
main()函数的负担,使程序结构简明清晰、便于维护,同时增强代码的复用性
// void:函数没有返回值
void display() {
printf("hello!\n");
} // 此函数执行结束后,回到main()函数
int main(int argc, const char * argv[]) { // 先执行main函数
display(); // 函数调用,数据流立即跳转到display()函数中
return 0;
}
-
3.一个C程序由一个或多个函数组成,函数之间可以相互调用,但不能调用
main()函数-
标准函数,库函数:
scanf() printf() getchar() putchar() strlen() strcpy() -
用户自定义函数:
display() add() sum() getValue() my_strcat() my_strcmp()
-
1.函数的构成
-
1.1 函数的定义
- C 语言要求函数要先定义、后调用,函数定义时必须指定函数名、参数类型、要实现的功能等,编译系统在调用函数时会根据函数定义的功能来执行
// 类型说明符:函数的返回值类型,若函数无返回值,返回值类型为 void // 形参列表:函数所需要的参数,在函数调用中传递数据。若函数不需要传递数据,可省略参数,但括号不能省略 // {} 内部是函数体,即该函数具体要执行的功能 类型说明符 函数名(形参列表) { 声明部分; 语句部分; }- 案例分析:定义一个函数,实现两个整数的求和运算
// 方法1:无参数、无返回值的函数。关键字 void 表示函数没有返回值,函数名后方括号是空的,没有任何参数 void add() { int a, b, ret; scanf("%d%d", &x, &y); ret = x + y; printf("%d", result); } // 方法2:有参数、无返回值的函数。调用该函数时,主调函数把实际参数的值传递给 a 和 b void add(int a, int b) { int ret; ret = x + y; printf("%d", result); } // 方法3:有参数、有返回值的函数。int 表示函数有返回值,且类型为整型 int add(int a, int b) { int ret; ret = x + y; // return 语句的作用是将 ret 的值作为函数返回值,变量 ret 的类型必须与返回值的类型相同 return ret; } -
1.2 函数的参数
-
参数的分类
-
形式参数:在函数定义时指定的参数,简称形参,如以下
max()函数中的x 和 y。实际上,若定义了max()函数而不去调用的话,它的参数x 和 y只是形式上存在的,不会向内存申请空间(即没有真实存在),所以称为形式参数 -
实际参数:在函数调用时传递的数据,简称实参,如以下
main()函数的a 和 b。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化
-
-
案例分析1:输出两个整数中的较大值
-
函数
max()被调用前,形参x 和 y并不占用内存空间 -
函数
max()被main()函数调用时,形参x 和 y被分配内存空间,并被实参a 和 b分别赋值 -
函数
max()执行期间,由于形参x 和 y已有值,所以可执行x > y? x:y操作,并将结果返回 -
函数
max()执行结束后,形参x 和 y占用的内存空间被释放
// 代码中实参传值给形参属于“值传递” #include <stdio.h> int max(int x, int y) { int z = 0; z = x > y? x:y; return z; } int main(int argc, const char * argv[]) { int a = 0, b = 0, c = 0; printf("input a、b:"); scanf("%d %d", &a, &b); c = max(a, b); // 函数调用,实参 a 的值传递给形参 x,实参 b 的值传递给形参 y,函数执行结束后的返回值传递给 c printf("较大值: %d", c); return 0; }![image]()
-
-
案例分析2:验证“值传递”方式中形参值的变化不影响实参
-
实参
num1的初值为10,实参num2的初值为20,调用函数swap()后,num1 和 num2的值并没有交换 -
实参
num1 和 num2分别传值给形参x 和 y,x的值为 10,y的值为 20。当函数swap()执行结束后,形参x 和 y的值分别为 20 和 10,但是实参num1 和 num2的值仍未改变,说明此处函数调用对实参num1 和 num2未起到交换数值的作用 -
代码中参数的传递方式为“值传递”(单向传递),当函数调用结束后,形参占用的内存空间被释放,所以形参不能传递值给实参,形参值的变化当然不会影响实参
#include <stdio.h> void swap(int x, int y) { int temp; printf("函数内部 交换之前:x=%d, y=%d\n", x, y); temp = x; x = y; y = temp; printf("函数内部 交换之后:x=%d, y=%d\n", x, y); } int main(int argc, const char * argv[]) { int num1,num2; scanf("%d%d",&num1,&num2); printf("交换之前:num1=%d, num2=%d\n", num1, num2); swap(num1, num2); printf("交换之后:num1=%d, num2=%d\n", num1, num2); return 0; } -
-
注意事项
-
1.函数的形参可以有多个,用
,分隔,类型相同或不同均可。相同类型时不可随意简写,如int max(int x, int y)不可简写为int max(int x, y) -
2.形参必须是变量,实参可以是变量、常量或表达式,且必须要有确定的值
-
3.实参类型必须与形参类型兼容,在符合数据类型转换规则的前提下,系统会按照赋值转换规则进行类型转换,如
float转为int、char转为int等 -
4.函数进行普通“值传递”时,将实参值传递给形参,传递方向为单向。传递结束后,若在函数中修改形参的值,并不会反作用于实参,两者在内存中分别占用不同的内存单元
-
5.参数的传递方式分为“值传递”和“地址传递”。“地址传递”方式中实参传递给形参的是地址,即实参和形参指向同一个内存空间
-
-
-
1.3 函数的返回值
-
返回值情型
-
若函数没有返回值,函数定义时的返回类型为
void,函数体中不能使用return语句 -
若希望获取函数调用后的执行结果,并在
main()函数中使用,则函数必须使用return语句将结果返回给主调函数。即若函数需要返回值,函数中必须有return语句,且返回值与函数定义时的函数类型一致
-
-
一般格式:
return (表达式); 或 return 表达式; -
注意事项
-
1.一个函数可以有多条
return语句,分别为指定条件返回各自的值。执行完第1个return语句后便终止函数的执行,将结果返回给主调函数并继续执行主调函数中其他语句 -
2.函数默认的返回类型为int,可省略函数类型说明符,但不建议这么做
-
3.若
return的类型和函数类型不一致,以函数类型说明符为准 -
4.若函数没有
return语句,则返回一个不确定的值。建议需要函数返回值时就加上函数的类型,不需要返回值时定义函数类型为void
-
-
案例分析:分析程序中函数的调用过程和运行结果
函数add()中执行x + y操作后,return语句将变量result的值返回给主调函数main(),并且赋值给main()函数中的变量sum
#include <stdio.h> int add(int x, int y) { int result; result = x + y; return result; } int main(int argc, const char * argv[]) { int sum; sum = add(5, 10); printf("%d\n", sum); return 0; }![image]()
-
2.函数的调用与声明
-
2.1 函数普通调用
-
一般格式:无参函数的调用语法为
函数名(),有参函数的调用语法为函数名(实参列表) -
调用位置与方式
- 1.函数语句:将函数调用当做一条语句,即
函数名();,最熟悉的调用方式
// 函数有或没有返回值都可以这样调用 max(a, b); calcu(n); // 该调用方法只执行函数中的语句,不使用返回值- 2.函数表达式:函数出现在表达式中,要求函数返回一个确定的值以参与表达式运算
// 函数有返回值才可以这样调用,将函数的调用作为一个值参与表达式运算 c = 2 * max(a, b) + 5; // void型函数不允许使用这种方式- 3.作为函数参数:把函数调用作为另外一个函数的参数
// 函数有返回值才可以这样调用,将函数的调用作为函数实参传递给函数形参 d = max(max(a, b), c); printf("处理的结果 %d\n", add(5, 10)); // void型函数不允许使用这种方式 - 1.函数语句:将函数调用当做一条语句,即
-
注意事项
-
1.
()是函数调用操作符。若函数无参数,则括号内为空,但不能省略 -
2.多个参数之间使用逗号分隔
-
3.实参个数与形参相同,且类型赋值兼容
-
-
案例分析
- 1.无参函数调用流程分析
// 程序从 main() 函数开始执行,输出第 1 行字符串“fun函数调用前” // main() 函数中第 2 行语句调用 fun() 函数,程序转至 fun() 函数 // fun() 函数执行结束后,程序返回原来的调用点,即main() 函数中第 2 行,继续从第 3 行开始执行,直至 main() 函数结束 #include <stdio.h> void fun() { printf("这是一个无参函数\n"); } int main() { printf("fun函数调用前\n"); fun(); printf("fun函数调用后\n"); return 0; }![image]()
- 2.输入 n 的值,计算 1 ~ n 的整数和
#include <stdio.h> int calcu(int x) { // 求和函数 int i = 0, total = 0; for(i = 1; i <= x; i++) { total += i; } return total; } int main(int argc, const char * argv[]) { int n = 0; scanf("%d", &n); printf("1+2+3+...+%d = %d", n, calcu(n)); // 传递n的值给形参x return 0; }
-
-
2.2 函数的嵌套调用
-
定义:函数的定义是独立的,在一个函数中不能定义另外一个函数。但函数可以嵌套调用,即可以在一个函数中调用另一个函数
-
案例:函数嵌套调用程序的运行过程与结果分析
-
main()函数是程序的入口,一般不会像其他自定义函数显式调用,而是由编译器进行隐性调用。程序从main()函数开始执行,调用fun1()函数,执行fun1()函数中的代码 -
调用
fun2()函数,程序流程转向fun2()函数,执行fun2()函数中的代码,直至fun2()函数执行结束 -
返回到
fun1()函数中调用fun2()函数的位置,继续执行fun1()函数中的语句,直至fun1()函数执行结束 -
返回到
main()函数中调用fun1()函数的位置,继续执行main()函数,直至main()函数执行结束
#include <stdio.h> void fun2() { printf("调用fun2()函数\n"); } void fun1() { printf("调用fun1()函数\n"); fun2(); } int main() { fun1(); return 0; }![image]()
-
-
-
2.3 函数的递归调用
-
定义:在函数的嵌套调用中,如果函数嵌套调用的是自己,就是函数的递归调用
-
特征
-
1.递归调用必须要有终止条件,无限递归会导致程序崩溃,通常在函数定义时通过
if语句控制 -
2.实现递归算法的关键是找出递归公式(即递归关系和递归终止条件),根据公式找出下一项,直到得到结果
-
3.递归算法容易理解,但递归有时会增加程序运行时间,消耗大量内存。用户应当权衡程序的运行时间与内存的占用情况,在程序的易读性和性能之间做出抉择
-
-
案例1:函数递归调用程序的运行过程与结果分析。计算 1 ~ n 的整数之和
-
递推:把大问题不断拆成更小的同类问题,一直拆到最简单的已知结果。要计算
1 ~ n的整数之和,需要计算1 ~ n - 1的整数之和,要计算1 ~ n - 1的整数之和,需要计算1 ~ n - 2的整数之和,以此类推,直到递推至n = 1时的和为 1 -
回归:从最小问题的结果出发,反向代入,一步步算出上层问题的结果。由
n = 1的和得到1、2之和(1 + 2 = 3),然后用这个结果加 3,得到 1 ~ 3 的整数之和(1 + 2 + 3 = 6),以此类推,直到得到1 ~ n - 1的整数之和。最后将该结果加 n,得到1 ~ n的整数之和(1 + 2 + 3 + ... + n - 1 + n)。递归公式为:
![image]()
#include <stdio.h> int fun(int n) { if(n == 1) { return 1; } else { return n + fun(n - 1); } } int main() { int sum; sum = fun(4); printf("1~ 4 的整数和是 %d\n", sum); return 0; }![image]()
-
-
案例2:输入正整数 n 的值,求
n!-
阶乘的数学定义:
n! = 1 * 2 * 3 * ... * (n - 1) * n,规定0! = 1,1! = 1
![image]()
-
递推:以求
4!为例-
根据公式,需要先求出
3!,即f(4) = 4 * f(3) -
求
3!,需要先求出2!,即f(3) = 3 * f(2) -
求
2!,需要先求出1!,即f(2) = 2 * f(1) -
求
1!,触发递归终止条件,直接得到结果f(1) = 1
-
-
回归
-
已知
f(1) = 1,回代计算f(2) = 2 * 1 = 2 -
将
f(2) = 2回代计算f(3) = 3 * 2 = 6 -
将
f(3) = 6回代计算f(4) = 4 * 6 = 24,最终得到4! = 24
-
#include <stdio.h> float fac(int n) { float f = 0.0f; if(n <= 1) { // 避免无限递归 f = 1; } else { f = n * fac(n - 1); } return f; } int main(int argc, const char * argv[]){ int n = 0; float result = 0.0f; printf("input an integer:"); scanf("%d", &n); result = fac(n); printf("result=%.2f\n", result); return 0; }![]()
-
-
案例3:输入正整数 n,求斐波那契数列第 n 项的值
- 斐波那契数列,又称黄金分割数列,数列的第一个数是 1,第二个数是 1,后续每个数都是前面两个数的和
- 随着 n 值的增加,递归函数调用的次数将大幅度增加。例如当
n = 10时,调用f() 函数为109次,而当n = 20时,调用f() 函数达到13529次
![image]()
#include <stdio.h> int cnt = 0; // 全局变量,用于统计递归函数调用的次数 int f(int n) { cnt++; if(n == 1 || n == 2) { return 1; } else { return f(n - 1) + f(n - 2); } } int main(int argc, const char * argv[]){ int n = 0, result = 0; scanf("%d", &n); result = f(n); printf("fib(%d) = %d\n", n, result); printf("递归次数=%d\n", cnt); return 0; }![image]()
-
案例4:递归经典问题之汉诺塔,有 A、B、C 三根柱子,A 柱上有 n 个大小不同的盘子,大盘在下、小盘在上。要求把所有盘子从 A 移到 C,每次只能移动 1 个盘子,大盘不能放在小盘上面
-
递归思想:把 n 个盘子的问题,拆解成
n - 1个盘子的子问题。终止条件为当n = 1时,直接把盘子从 A 移动到 C,程序结束-
1.把 A 上的
n ‑ 1个盘子,借助 C,移到 B -
2.把 A 剩下的最大 1 个盘子,直接移到 C
-
3.把 B 上的
n ‑ 1个盘子,借助 A,移到 C
-
#include <stdio.h> int hanoi(int n, char x, char y, char z){ // n:塔的数量 a、b、c位柱子的名称 if(n > 0){ hanoi(n-1, x, z, y); printf("moving from %c to %c\n", x, z); hanoi(n-1, y, x, z); } return 0; } int main(int argc, const char * argv[]){ int num = 0; char a, b, c; printf("请输入塔的数量和三个柱子的名称:\n"); scanf("%d %c %c %c", &num, &a, &b, &c); hanoi(num, a, b, c); return 0; } -
-
-
2.4 函数声明
-
一般格式:
类型说明符 函数名(形参列表);,函数定义时的首行称为函数原型,函数声明时只需要在函数的原型后面加上; -
作用:把函数的信息(函数名、函数类型、参数的个数和类型)提前告知编译系统,在编译时对函数调用检查合法性,非法函数调用会编译报错
-
声明位置
- 1.函数声明在主调函数的外部,此时函数可以被声明之后出现的所有函数调用
#include <stdio.h> int add(int, int); int main(int argc, const char * argv[]) { int sum; sum = add(5, 10); printf("%d\n", sum); return 0; } int add(int x, int y) { int result; result = x + y; return result; }- 2.函数声明在主调函数的内部,此时函数只能被主函数调用
#include <stdio.h> int main(int argc, const char * argv[]) { int add(int, int); int sum; sum = add(5, 10); printf("%d\n", sum); return 0; } int add(int x, int y) { int result; result = x + y; return result; } -
注意事项
-
1.为提高程序可读性,建议把函数原型写在
main()函数前,函数定义写在main()函数后。若函数定义在main()函数前,可省略函数原型 -
2.函数声明也称为函数原型,可写在主调函数中
-
3.函数声明中可省略形参名称,只写形参类型,如
int max(int, int);编译系统不检查形参名
-
-
3.数组作为函数参数
-
3.1 数组元素作为函数参数
-
值传递:实参和形参占用不同的存储单元,只是存储的值相同。数组元素作为函数参数传递,本质上传的是元素数值,与普通变量作为参数的行为类似,函数内部修改参数不会影响原数组的元素值
-
案例分析
- 1.找出数组中的最大值
#include <stdio.h> int max(int x, int y) { // 计算两数较大值的函数 return x > y? x : y; } int main(int argc, const char * argv[]) { int a[10] = {0}; int i = 0, m = 0; printf("input 10 integers:\n"); for (i = 0; i < 10; i++) { scanf("%d", &a[i]); } m = a[0]; // 假设下标为 0 的元素为最大值,后续遍历下标为 1 ~ N - 1 的元素 for (i = 1; i < 10; i++) { m = max(m, a[i]); // 调用函数 } printf("max is %d", m); return 0; }
-
-
3.2 数组名作为函数参数
-
地址传递
-
因为数组名表示数组的首地址,所以实参传递地址给形参,形参和实参所代表的数组共享同一段内存单元
-
若在函数中改变数组元素值,则修改了内存单元中存储的数据,实参数组对应元素值也会被修改
-
编译器只关心数组的首地址,不关心数组长度,因此会加一个函数形参来接收数组长度
-
-
案例分析
- 1.编写一个函数显示数组的值
#include <stdio.h> void display(int b[], int n) { int i = 0; for (i = 0; i < n; i++) { printf("%4d", b[i]); } } int main(int argc, const char * argv[]) { int a[5] = {10, 20, 30, 40, 50}; display(a, 5); // 数组名作为实参 return 0; }- 2.某同学一学期有 5 门课程,根据每门课程的得分情况,找出其最高分和最低分
// 定义一个函数 min 用来求最低分,定义一个函数 max 用来求最高分,如 int min(int array[5]) // main()函数调用 min 函数 min(array),其中实参 score 是一个整型数组名 #include <stdio.h> int main(int array[5]) { }
-
-
3.3 多维数组作为函数参数
-
注意事项
-
数组名作为函数参数传递时传递的是数组的首地址
-
函数形参若是二维数组,可省略第一维的大小,不能省略第二维的大小,否则函数无法确定所传数组的行列数
-
实参数组可以与形参数组的第一维大小不同,在函数形参中常添加一个整型参数表示二维数组第一维的大小
-
-
案例分析
- 1.将一个
4 * 4的矩阵转置
// 分析:矩阵转置就是第i行第j列的元素与第j行第i列的元素交换 #include <stdio.h> void reverse(int b[][4], int n) { // 实现矩阵转置 int i = 0, j = 0, temp = 0; for (i = 0; i < n; i++) { for (j = 0; j <= i; j++) { // 只遍历数组主对角线及左下角的元素 temp = b[i][j]; bi[][j] = b[j][i]; b[j][i] = temp; } } } void printfarr(int a[][4], int n) { // 实现打印数组元素 int i = 0, j = 0; for(i = 0; i < 4; i++) { for(j = 0; j < 4; j++) { printf("%4d", a[i][j]); } printf("\n"); } } int main(int argc, const char * argv[]) { int a[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; printf("输出转置前的矩阵:\n"); printfarr(a, 4); reverse(a, 4); printf("\n输出转置后的矩阵:\n"); printfarr(a, 4); return 0; } - 1.将一个
-
4.变量的作用域
-
4.1 局部变量
-
定义:在函数内部或代码块中(
{}内部)声明的变量,包括函数的形参 -
作用域:变量所在的局部范围;仅在包含该变量声明的函数中才起作用,在函数外不能使用
-
注意事项
-
1.局部变量的生命周期为从定义开始,到函数或代码块结束
-
2.在程序的各个部分允许使用同名变量,实参和形参名也允许同名,如案例分析3
-
3.
main()函数中的变量也是局部变量 -
4.允许在复合语句(分程序或程序块)中定义变量,它仅在复合语句内有效,如案例分析1
-
-
案例分析
- 1.分析以下代码的执行情况
#include <stdio.h> int main(int argc, const char * argv[]) { int i = 0; // i 的作用域是整个 main() 函数 for (i = 0; i < 3; i++) { ; } printf("%d\n", i); return 0; } // 可正常编译运行 #include <stdio.h> int main(int argc, const char * argv[]) { for (int i = 0; i < 3; i++) { ; } printf("%d\n", i); // 超出了i的作用域 return 0; } // 编译报错,提示变量 i 未声明 // 变量 i 在for循环中声明,因此其生效范围仅在 for 循环中![image]()
- 2.分析以下代码的执行情况
#include <stdio.h> void local_value() { int a = 1, b = 2; // a、b的作用域仅限于local_value函数中 printf("%d %d", a, b); } int main(int argc, const char * argv[]) { printf("%d %d", a, b); return 0; } // 编译报错,main() 函数中并未定义变量 a、b![]()
- 3.分析以下代码的执行情况
#include <stdio.h> void local_value() { int a = 1, b = 2; // a、b的作用域仅限于local_value函数中 printf("%d %d", a, b); } int main(int argc, const char * argv[]) { int a = 5; // a、b的作用域仅限于main()函数中 float b = 2.5; printf("%d %f\n", a, b); return 0; } // 可正常编译运行 -
函数的地址
-
定义:函数名本质上是指向函数代码起始地址的常量指针,可通过打印函数名或使用函数指针来获取函数的地址
-
与变量作用域的关系:正是因为不同函数在内存中的地址不同,即占用的内存空间不同,所以才允许在不同函数中使用相同的变量名
![]()
-
案例分析
#include <stdio.h> void local_value() { int a = 1, b = 2; printf("%d %d", a, b); } int main(int argc, const char * argv[]) { int a = 5; float b = 2.5; printf("main函数的地址为: %p\n", (void *)main); // 将函数指针强制转换为void*类型以匹配%p格式说明符 printf("local_value函数的地址为: %p\n", (void *)local_value); return 0; }![]()
-
-
-
4.2 全局变量
-
定义:在函数外部(
{}外部)定义的变量,也称外部变量或全程变量 -
作用域:整个工程;从定义位置到文件结束,在这个范围的任何地方都可使用,包括函数内
-
注意事项
-
1.全局变量的生命周期即整个程序的生命周期
-
2.全局变量的作用域使得其值容易修改,降低了程序的可读性,容易产生逻辑错误
- 模块之间高耦合度导致某个模块的修改牵涉到其他模块修改,增加了代码的脆弱性和复杂性
-
3.允许局部变量和全局变量重名但不建议这么做,在局部变量作用域内全局变量被"屏蔽"
-
4.程序设计要满足"高内聚,低耦合",全局变量破坏了这一要求
-
-
案例分析
- 1.分析以下代码的执行情况
#include <stdio.h> int a = 5, b = 10; // 全局变量a、b void modify() { int a = 5, b = 10; // 局部变量a、b,此时全局变量a、b被屏蔽 a++; b--; printf("pos1: a=%d, b=%d\n", a, b); // a的值为 6,b的值为 9 } int main(int argc, const char * argv[]) { modify(); printf("pos2: a=%d, b=%d\n", a, b); // a的值为 5,b的值为 10 return 0; }
-
5.变量的存储类别
-
5.1 存储方式
-
时空分析
-
时间角度:生存期,静态存储方式和动态存储方式
-
空间角度:作用域,局部变量和全局变量
-
-
内存划分
-
栈区
-
存放局部变量和函数调用时的上下文信息,如现场保护、返回地址等
-
容量有限,递归过深或局部变量过大都可能会导致栈溢出
-
-
堆区
-
程序运行时动态分配的内存(通过
malloc/calloc/realloc申请) -
适合存储生命周期不确定的数据,如链表、动态数组
-
程序员负责申请和释放(
free)内存,忘记释放会导致内存泄露
-
-
静态区
-
存放静态变量和全局变量
-
程序执行期间始终占据固定存储单元
-
-
-
变量属性
-
数据类型:定义变量的取值范围和允许的操作
-
存储类别:数据在内存中存储分配和释放的时机以及存储结构
-
-
-
5.2 自动变量
-
定义:函数中的局部变量未加
static声明就属于自动变量,包括函数内的局部变量、函数形参和复合语句中的变量 -
注意事项
-
1.调用函数时为自动变量分配内存,调用结束后释放内存
-
2.声明关键字为
auto,如auto int z;,可省略auto -
3.
C 标准(如 C89、C99、C11)明确规定,函数形参列表中不能显式使用存储类别说明符,如auto
-
-
-
5.3 静态变量
-
1.静态局部变量
-
定义:使用
static关键字修饰的局部变量 -
特征:属于静态存储类型,在静态存储区分配内存;程序运行之初就分配内存,而非函数调用时,运行结束后释放
-
适用场合
-
1.保留调用后的变量值
-
2.确定某函数的调用次数
-
-
注意事项
-
1.未初始化的静态局部变量系统自动为其初始化为0
-
2.静态局部变量的初始化在编译时进行,可通过汇编代码查看,静态局部变量的部分没有汇编代码。也可以分步调试查看,调试期间会自动跳过静态变量所属语句
![]()
-
3.静态局部变量在程序运行期间都存在,作用域仍为函数内部
-
4.静态局部变量长期占用内存,应尽量少用
-
-
案例分析
- 1.分析以下代码的执行情况
#include <stdio.h> int i = 0; void sub() { int a = 0; static int b = 0; // 静态局部变量b,保留上一次函数调用后的值 a++; b++; printf("%d: a=%d, b=%d\n", i, a, b); } int main(int argc, const char * argv[]) { for(i = 1; i <= 3; i++) { sub(); } return 0; } // 1: a=1, b=1 // 2: a=1, b=2 // 3: a=1, b=3
-
-
2.静态全局变量
-
全局变量的外部链接属性变成了内部链接属性
-
其他
.c源文件不能再使用该变量,使用时感觉作用域变小
-
-
3.静态函数
-
定义:用
static关键字修饰的函数 -
特征:只能在定义它的源文件内部被调用,其他源文件无法对其调用,即便其他文件使用
extern声明也不行 -
注意事项
-
不同源文件有同名的静态函数并不会引发冲突,因为它们的作用域相互独立
-
静态函数具有内部链接属性,它不会被链接器导出,不会和其他文件中同名的函数产生冲突
-
-
-
-
5.4 寄存器变量
-
定义:存储在CPU的寄存器中的变量,用
register修饰 -
作用:访问寄存器的速度远高于访问内存的速度,因此能够提高程序运行效率
-
注意事项
-
1.只有局部变量和形式参数才允许声明为寄存器变量,通过不断分配与释放寄存器可提高寄存器利用率
-
2.寄存器变量的数量受到计算机寄存器数目的限制
-
3.多数系统只允许定义
int、char和指针类型变量为寄存器变量;多数系统将register作为自动变量处理,存储在内存中 -
4.若部分未定义为寄存器类别的变量使用频繁,编译系统自动将之存放在寄存器中
-
-
-
5.5 外部变量
6.函数应用实例
- 1.将序列的最大值和第 1 个数交换,最小值和最后一个数交换,并输出新序列
// 分析:遍历数组,定位最大值和最小值的下标,完成交换
// 注意1:代码通过最值的下标定位到最值,而不是直接记录最值的数值,因为数组中对元素的引用借助下标实现
// 注意2:第1个数恰好是最小值的特殊情况
#include <stdio.h>
void exchange(int b[], int n) { // 完成交换的核心代码
// max 为最大值下标,min 为最小值下标,初值均为0,即假设下标为 0 的数为最大/小值
int max = 0, min = 0;
int i = 0, temp = 0;
for (i = 1; i < n; i++) {
if (b[i] > b[max]) {
max = i;
}
if (b[i] < b[min]) {
min = i;
}
}
printf("最大值为 %d, 最小值为 %d\n", b[max], b[min]);
// 最大值和下标为 0 的数交换
temp = b[0];
b[0] = b[max];
b[max] = temp;
// 若下标为 0 的位置恰好是最小值,它被替换到了下标为 max 的位置
if (0 == min) {
min = max;
}
// 最小值和下标为 n - 1 的数交换
temp = b[n - 1];
b[n - 1] = b[min];
b[min] = temp;
}
void print_arr(int a[], int n) { // 完成打印数组元素的核心代码
int i = 0;
while (i < n) {
printf("%4d", a[i]);
i++;
}
printf("\n");
}
int main(int argc, const char * argv[]) {
int a[10] = {-5, 3, 8, 9, -1, -3, 5, 6, 0, 4};
int i = 0;
printf("交换前: ");
print_arr(a, 10);
exchange(a, 10);
printf("\n交换后: ");
print_arr(a, 10);
}
- 2.编写一个函数,在一个整数序列中查找某个整数,若存在,返回该整数在序列中的位置;否则返回 -1 。要求在主函数中调用,输出结果
// 分析:遍历数组,逐个对比当前数组元素与目标值看是否相等
// 本题可延伸至二分查找,具体参考案例分析4
#include <stdio.h>
int search(int a[], int n, int target) { // 实现查找的核心代码
int i = 0;
for (i = 0; i < n; i++) {
if (a[i] == target) {
return i; // 找到目标,返回下标
}
}
return -1; // 未找到目标
}
int main(int argc, const char * argv[]) {
int arr[] = {10, 20, 30, 40, 50};
int size = sizeof(arr) / sizeof(arr[0]);
int target = 30;
int result = search(arr, size, target);
if (result != -1) {
printf("目标整数 %d 在数组中的位置是: %d\n", target, result);
}
else {
printf("目标整数 %d 不在数组中\n", target);
}
return 0;
}
- 3.编写一个函数,将字符串转换成相应整数。转换时,遇到非数字字符停止转换。要求在主函数中调用,输出结果。例如:"123"——>123;"12a3"——>12;"a123"——>0
#include <stdio.h>
#include <ctype.h>
int convert(char s[]) {
int result = 0;
int i = 0;
// 跳过前导空格
while (s[i] == ' ') {
i++;
}
// 处理正负号(默认为正数)
int sign = 1;
if (s[i] == '-') {
sign = -1;
i++;
}
else if (str[i] == '+') {
i++;
}
// 转换数字字符,遇到非数字则停止
while (isdigit(s[i])) {
result = result * 10 + (s[i] - '0');
i++;
}
return sign * result;
}
int main(int argc, const char * argv[]) {
char test1[] = "123";
char test2[] = "12a3";
char test3[] = "a123";
char test4[] = " -456xyz";
char test5[] = " +789";
printf("%s -> %d\n", test1, convert(test1));
printf("%s -> %d\n", test2, convert(test2));
printf("%s -> %d\n", test3, convert(test3));
printf("%s -> %d\n", test4, convert(test4));
printf("%s -> %d\n", test5, convert(test5));
return 0;
}
- 4.现有一按升序排列的数组
{3, 9, 10, 13, 16, 27, 30},输入正整数x,使用二分查找的方式查询它是否在数组中
// 代码1
#include <stdio.h>
int main(int argc, const char * argv[]) {
// insert code here...
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int k = 17, sz = sizeof(arr) / sizeof(arr[0]);
int left = 0, right = sz - 1;
while (left <= right) { // 中间有元素被查找,要带等于
// int mid = (left + right) / 2; // mid的求取在循环内
int mid = left + (right - left) / 2; // 避免left + right越界溢出,将right比left多的部分的一半再加到left上,这样两者就一样了
if (arr[mid] < k) {
left = mid + 1;
}
else if (arr[mid] > k) {
right = mid - 1;
}
else {
printf("找到了, 下标是: %d\n", mid);
break;
}
}
if (left > right) {
printf("找不到\n");
}
return 0;
}
// 代码2
#include <stdio.h>
int main(int argc, const char * argv[]){
int a[7] = {3, 9, 10, 13, 16, 27, 30};
int left = 0, right = 6;
int mid = 0, x = 0;
printf("请输入要查找的数:\n");
scanf("%d", &x);
while(left <= right){
mid = (left + right) / 2;
if(a[mid] > x){
right = mid - 1;
}
else if(a[mid] < x){
left = mid + 1;
}
else{
printf("查找的数据在第%d个\n", mid + 1);
return mid;
}
}
printf("查找的数据不存在!\n");
return 0;
}
// 代码3
#include <stdio.h>
int binary_sort(int s[], int n, int x){
int left = 0, right = n - 1, mid;
while(left <= right){
mid = (left + right) / 2;
if(s[mid] > x){
right = mid - 1;
}
else if(s[mid] < x){
left = mid + 1;
}
else{
return mid;
}
}
return -1;
}
int main(int argc, const char * argv[]){
int a[7] = {3, 9, 10, 13, 16, 27, 30};
int index = 0, x = 0;
printf("请输入要查找的数:\n");
scanf("%d", &x);
index = binary_sort(a, 7, x);
if(index >= 0){
printf("要查找的 %d 下标为 %d\n", x, index);
}
else {
printf("查找的数据不存在!\n");
}
return 0;
}
















浙公网安备 33010602011771号