函数的嵌套调用
核心规则
- 不允许嵌套定义:函数不能在另一个函数内部定义。
- 允许嵌套调用:在被调函数内部可以调用其他函数。
正确示例
void a() {
// 函数a的实现
}
void b() {
a(); // 在b中调用a,属于嵌套调用
}
int main() {
b(); // 主函数调用b
return 0;
}
错误示例
void a() {
void b() { // 错误:在a内部定义b
// 函数b的实现
}
}
嵌套调用案例
案例 1:判断素数
#include <stdio.h>
// 判断是否为素数
int is_prime(int n) {
int flag = 1;
for (int i = 2; i <= n / 2; i++) {
if (n % i == 0) {
flag = 0;
break;
}
}
return flag;
}
int main() {
for (int i = 3; i <= 100; i++) {
if (is_prime(i)) { // 调用素数判断函数
printf("%-4d", i);
}
}
return 0;
}
案例 2:数组元素查找
#include <stdio.h>
// 查找元素在数组中的位置
int find_index(int arr[], int len, int n) {
int index = -1;
for (int i = 0; i < len; i++) {
if (arr[i] == n) {
index = i;
break;
}
}
return index;
}
int main() {
int arr[] = {11, 22, 33, 44, 55};
int len = sizeof(arr) / sizeof(arr[0]);
int index = find_index(arr, len, 33); // 调用查找函数
// 输出结果
return 0;
}
案例 3:求 4 个数的最大值
#include <stdio.h>
// 求两个数的最大值
int max_2(int a, int b) {
return a > b ? a : b;
}
// 求四个数的最大值(嵌套调用max_2)
int max_4(int a, int b, int c, int d) {
int max_ab = max_2(a, b);
int max_cd = max_2(c, d);
return max_2(max_ab, max_cd);
}
int main() {
int a, b, c, d;
scanf("%d%d%d%d", &a, &b, &c, &d);
printf("最大值:%d\n", max_4(a, b, c, d));
return 0;
}
函数的递归调用
定义
在一个函数中直接或间接调用自身的调用方式。
递归的底层实现
依赖程序调用栈(Call Stack),通过栈帧的创建与销毁管理多次调用:
- 栈帧存储内容:函数参数、局部变量、返回地址。
- 流程示例(计算 3!):
- 调用
fac(3)
→创建栈帧 1(n=3)。 - 调用
fac(2)
→创建栈帧 2(n=2)。 - 调用
fac(1)
→创建栈帧 3(n=1),触发终止条件返回 1。 - 销毁栈帧 3→栈帧 2 计算
2*1=2
并返回。 - 销毁栈帧 2→栈帧 1 计算
3*2=6
并返回。 - 销毁栈帧 1→主函数得到结果 6。
- 调用
递归四要素
- 必须有出口:明确终止条件,避免死循环。
- 先判断出口:在递归调用前检查终止条件。
- 递归调用自身:函数内部调用自身。
- 向出口逼近:每次调用需缩小问题规模。
递归案例
案例 1:求年龄
#include <stdio.h>
int age(int n) {
int _age;
if (n == 1) { // 出口:第1个人10岁
_age = 10;
} else if (n > 1) {
_age = age(n - 1) + 2; // 递归调用,向出口逼近
}
return _age;
}
int main() {
printf("第5个人的年龄:%d\n", age(5)); // 输出18
return 0;
}
案例 2:求阶乘
#include <stdio.h>
size_t fac(int n) {
if (n <= 0) {
printf("n不能为0及以下!\n");
return -1;
} else if (n == 1) { // 出口:1! = 1
return 1;
} else {
return n * fac(n - 1); // 递归调用
}
}
int main() {
size_t n;
scanf("%lu", &n);
printf("%lu的阶乘:%lu\n", n, fac(n));
return 0;
}
案例 3:快速排序
#include <stdio.h>
void QSort(int arr[], int n) {
if (n <= 1) return; // 出口:数组长度≤1时无需排序
int i = 0, j = n - 1;
int pivot = arr[0]; // 选择首元素为基准值
while (i < j) {
while (i < j && arr[j] > pivot) j--;
while (i < j && arr[i] <= pivot) i++;
if (i < j) { // 交换元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准值放到正确位置
arr[0] = arr[i];
arr[i] = pivot;
// 递归排序左右子数组
QSort(arr, i); // 左子数组
QSort(arr + i + 1, n - i - 1); // 右子数组
}
int main() {
int arr[] = {23, 45, 56, 24, 78, 22, 19};
int n = sizeof(arr) / sizeof(arr[0]);
QSort(arr, n);
for (int i = 0; i < n; i++) {
printf("%-4d", arr[i]);
}
return 0;
}
数组做函数参数
传递规则
- 传递首地址:数组作为参数时,实际传递的是首地址,形参退化为指针。
- 地址传递特性:形参和实参指向同一块内存,修改形参会影响实参。
- 需额外传递长度:函数内部无法通过
sizeof
获取数组实际长度,需显式传递。
特殊情况
字符数组存放字符串时,无需传递长度,可通过\0
判断结束。
数组传参案例
案例 1:数组元素比较
#include <stdio.h>
#define LEN 5
int get_large(int x, int y) {
if (x > y) return 1;
else if (x < y) return -1;
else return 0;
}
int main() {
int a[LEN] = {12, 12, 10, 18, 5};
int b[LEN] = {111, 112, 110, 8, 5};
int max = 0, min = 0, equal = 0;
for (int i = 0; i < LEN; i++) {
int res = get_large(a[i], b[i]);
if (res == 1) max++;
else if (res == -1) min++;
else equal++;
}
printf("a>b次数:%d, a<b次数:%d, 相等次数:%d\n", max, min, equal);
return 0;
}
案例 2:求数组平均值
#include <stdio.h>
float get_avg(float scores[], int len) {
float sum = scores[0];
for (int i = 1; i < len; i++) {
sum += scores[i];
}
return sum / len;
}
int main() {
float scores1[] = {77, 88, 99, 66, 57};
float scores2[] = {67, 87, 98, 78, 67, 99, 88, 77, 77, 67};
int len1 = sizeof(scores1) / sizeof(scores1[0]);
int len2 = sizeof(scores2) / sizeof(scores2[0]);
printf("平均分1:%.2f, 平均分2:%.2f\n", get_avg(scores1, len1), get_avg(scores2, len2));
return 0;
}
变量的作用域
定义
变量的有效范围,即变量在程序中可被访问的区域。
变量分类
类型 | 定义位置 | 作用域 | 初始值 |
---|---|---|---|
全局变量 | 函数之外 | 从定义处到本源文件结束 | 整型 / 浮点型为 0,字符型为\0 ,指针为 NULL |
局部变量 - 形参 | 函数参数列表 | 函数内部 | 随机值 |
局部变量 - 函数内 | 函数内部 | 函数内部 | 随机值 |
局部变量 - 复合语句 | 复合语句(如{} )内部 | 复合语句内部 | 随机值 |
局部变量 - for 循环 | for 循环表达式 1 | 循环体内 | 随机值 |
注意事项
- 同名变量处理:遵循就近原则,局部变量会屏蔽同名全局变量。
- 全局变量优缺点:
- 优点:实现函数间数据共享,减少形参个数。
- 缺点:长期占用内存,降低程序通用性,违反高内聚低耦合原则,建议少用。
变量的生命周期
定义
变量在程序运行中的存在时间(从内存申请到释放的过程)。
存储类型
存储类型 | 修饰对象 | 存储区域 | 特性 |
---|---|---|---|
auto | 局部变量 | 动态存储区(栈 / 堆) | 局部变量默认类型 |
static - 局部变量 | 局部变量 | 静态存储区 | 生命周期延长,作用域不变 |
static - 全局变量 | 全局变量 | 静态存储区 | 作用域限制为本文件 |
static - 函数 | 函数 | 静态存储区 | 仅限本文件访问 |
extern | 全局变量 / 函数 | 静态存储区 | 扩展作用域,标识变量 / 函数在外部文件定义 |
static 关键字作用
- 修饰局部变量:延长生命周期至程序结束,作用域不变。
- 修饰全局变量:限制作用域为本文件,生命周期不变。
- 修饰函数:仅限本文件访问,类似私有函数。