C语言基础第12天:函数进阶与扩展

函数的嵌套调用

核心规则

  • 不允许嵌套定义:函数不能在另一个函数内部定义。
  • 允许嵌套调用:在被调函数内部可以调用其他函数。

正确示例

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),通过栈帧的创建与销毁管理多次调用:

  1. 栈帧存储内容:函数参数、局部变量、返回地址。
  2. 流程示例(计算 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. 必须有出口:明确终止条件,避免死循环。
  2. 先判断出口:在递归调用前检查终止条件。
  3. 递归调用自身:函数内部调用自身。
  4. 向出口逼近:每次调用需缩小问题规模。

递归案例

案例 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 关键字作用

  1. 修饰局部变量:延长生命周期至程序结束,作用域不变。
  2. 修饰全局变量:限制作用域为本文件,生命周期不变。
  3. 修饰函数:仅限本文件访问,类似私有函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值