CC语言通关
第07课 · 要写代码
教材对照
第07课配套 · 纯代码

第07课 · 要写代码

定义/调用/声明、值传递、递归、数组作参数、变量存储类别

32 段代码每行都有注释边敲边想
教材依据第7章《用函数实现模块化程序设计》书页 p.167–216
本课覆盖:教材第7章正文 + 书末习题(p.215–216)。标「教材 p.X」= 教材段落总结;「例题」= 教材例题;「习题」= 书末题目;「本课补充」= 非教材原文。
在全书对照表中查看 →

本章的每一个语法点都配一段完整可编译的程序。每行代码后面跟 // 注释,解释这一行在干什么。
建议用法:先照着敲一遍 → 编译运行 → 合上答案自己默写 → 再对照。

一、知识点代码

c
// ============ 1. 无参函数:定义 + 声明 + 调用(例 7.1)============
#include <stdio.h>                      // 包含标准输入输出头文件,才能用 printf
void print_star();                      // 函数声明:告诉编译器有这么一台"打星号机",末尾必须有分号

int main()                              // 主函数,程序入口
{
    print_star();                       // 调用无参函数,括号不能省;作为语句末尾要有分号
    printf("How do you do!\n");         // 输出中间那行文字
    print_star();                       // 再调用一次
    return 0;                           // 程序正常结束
}

void print_star()                       // 函数定义:首行末尾没有分号,后面紧跟花括号
{
    printf("****************\n");       // 输出 16 个星号并换行
}
c
// ============ 2. 有参函数与值传递(例 7.2)============
#include <stdio.h>                      // 标准输入输出
int max(int x, int y);                  // 函数声明:两个 int 进料口,一个 int 出料口

int main()
{
    int a = 12, b = -34, c;             // a、b 是实参所在的盒子
    c = max(a, b);                      // 把 a、b 的值复制给 x、y,再把返回值赋给 c
    printf("max is %d\n", c);           // 输出结果
    return 0;
}

int max(int x, int y)                   // 函数定义:x、y 是形参,调用时才临时分配内存
{
    int z;                              // 函数内部的临时变量(局部变量)
    z = (x > y) ? x : y;                // 条件表达式:x 大取 x,否则取 y
    return z;                           // 把 z 作为函数值带回主调函数
}
c
// ============ 3. 值传递是单向的:swap 改不了实参 ============
#include <stdio.h>                      // 标准输入输出
void swap(int x, int y)                 // 形参 x、y 是实参的"复印件"
{
    int t = x;                          // 三只杯子法第一步:把 x 倒进 t
    x = y;                              // 第二步:把 y 倒进 x
    y = t;                              // 第三步:把 t 倒进 y
}
int main()
{
    int a = 3, b = 8;                   // 实参
    printf("调用前:a=%d, b=%d\n", a, b);         // 输出 3 和 8
    swap(a, b);                         // 传的是 a、b 的值,不是它们本身
    printf("调用后:a=%d, b=%d\n", a, b);         // 依然是 3 和 8,形参怎么改都改不到实参
    return 0;
}
c
// ============ 4. 函数定义在调用之后,必须先声明(例 7.4)============
#include <stdio.h>                      // 标准输入输出
float add(float x, float y);            // 函数原型:函数的首行 + 一个分号

int main()
{
    float a = 3.6f, b = 6.5f;           // 两个实型实参,f 表示这是 float 常量
    printf("sum is %f\n", add(a, b));   // 函数调用直接作 printf 的实参,此时不能加分号
    return 0;
}

float add(float x, float y)             // 函数定义:首行末尾没有分号
{
    float z;                            // 局部变量,装结果
    z = x + y;                          // 求和
    return z;                           // 返回结果
}
c
// ============ 5. 函数类型决定返回值类型(例 7.3)============
#include <stdio.h>                      // 标准输入输出
int max(float x, float y);              // 函数类型是 int,但形参是 float

int main()
{
    float a = 1.5f, b = 2.6f;           // 两个实型数
    printf("max is %d\n", max(a, b));   // 输出 2 而不是 3 —— 2.6 被截断成 2
    return 0;
}

int max(float x, float y)               // 返回值类型由这里的 int 决定
{
    float z;                            // z 是 float 型
    z = (x > y) ? x : y;                // z 得到 2.6
    return z;                           // 返回时按赋值规则转成 int,即 2
}
c
// ============ 6. 函数的嵌套调用(例 7.5)============
#include <stdio.h>                      // 标准输入输出
int max4(int a, int b, int c, int d);   // 声明 max4

int main()
{
    printf("max= %d\n", max4(12, 45, -6, 89));   // 一次调用,内部会多次调用 max2
    return 0;
}

int max2(int a, int b)                  // 求两数中的大者(定义在 max4 之前,所以 max4 里不用声明)
{
    return (a >= b) ? a : b;            // 条件表达式直接返回
}

int max4(int a, int b, int c, int d)    // 求四数中的大者
{
    int m;                              // 存放"目前的最大者"
    m = max2(a, b);                     // 第 1 次嵌套调用:a、b 中的大者
    m = max2(m, c);                     // 第 2 次:a、b、c 中的大者
    m = max2(m, d);                     // 第 3 次:四者中的大者
    return m;                           // 返回给 main
}
c
// ============ 7. 递归入门:求第 n 个学生的年龄(例 7.6)============
#include <stdio.h>                      // 标准输入输出
int age(int n);                         // 递归函数也要声明

int main()
{
    printf("NO.5, age: %d\n", age(5));  // 函数调用作为 printf 的实参
    return 0;
}

int age(int n)
{
    int c;                              // 存放本层结果
    if (n == 1)                         // 终止条件:第 1 个学生已知 10 岁
        c = 10;
    else                                // 递归公式:比前一个学生大 2 岁
        c = age(n - 1) + 2;             // 这里又一次调用自己
    return c;                           // 把本层结果交给上一层
}
c
// ============ 8. 递归求 n!(例 7.7 的模板)============
#include <stdio.h>                      // 标准输入输出
int fac(int n);                         // 声明递归函数

int main()
{
    int n = 10;                         // 要求的阶乘数
    printf("%d! = %d\n", n, fac(n));    // 输出 10! = 3628800
    return 0;
}

int fac(int n)
{
    int f;                              // 存放本层结果
    if (n == 0 || n == 1)               // 终止条件:0! 和 1! 都是 1
        f = 1;
    else                                // 递归公式:n! = n × (n-1)!
        f = fac(n - 1) * n;
    return f;                           // 返回本层结果
}
c
// ============ 9. 数组元素作函数实参(值传递,改不了原数组)============
#include <stdio.h>                      // 标准输入输出
int addone(int x);                      // 形参是一个普通变量

int main()
{
    int a[5] = {1, 2, 3, 4, 5};         // 数组
    int i;
    for (i = 0; i < 5; i++)
        a[i] = addone(a[i]);            // 每次只把 a[i] 的"值"复制给 x
    for (i = 0; i < 5; i++)
        printf("%d ", a[i]);            // 输出 2 3 4 5 6
    printf("\n");
    return 0;
}

int addone(int x)                       // 收到的是 a[i] 的副本
{
    return x + 1;                       // 加 1 后返回(原数组元素不直接受影响)
}
c
// ============ 10. 一维数组名作函数参数(传首元素地址)============
#include <stdio.h>                      // 标准输入输出
void change(int a[], int n);            // 形参写成数组,n 是实际长度

int main()
{
    int b[5] = {1, 2, 3, 4, 5};         // 实参数组
    int i;
    change(b, 5);                       // 传的是 b 的首元素地址
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);            // 输出 11 12 13 14 15 —— 原数组被改了
    printf("\n");
    return 0;
}

void change(int a[], int n)             // 形参数组与实参数组共占同一段内存
{
    int i;
    for (i = 0; i < n; i++)
        a[i] = a[i] + 10;               // 改形参 = 改实参
}
c
// ============ 11. 二维数组名作函数参数(第二维不能省)============
#include <stdio.h>                      // 标准输入输出
int max_value(int array[][4]);          // 第一维可省,第二维必须写,且要与实参一致

int main()
{
    int a[3][4] = {{0, 3, 5, 7}, {2, 4, 6, 8}, {15, 17, 34, 12}};   // 3 行 4 列
    printf("Max value is %d\n", max_value(a));   // 输出 34
    return 0;
}

int max_value(int array[][4])           // 形参:列数必须与实参相同
{
    int i, j, max;
    max = array[0][0];                  // 先拿第一个当擂主
    for (i = 0; i < 3; i++)
        for (j = 0; j < 4; j++)
            if (array[i][j] > max)
                max = array[i][j];      // 谁大谁当擂主
    return max;                         // 返回最大值
}
c
// ============ 12. 局部变量:只在本书范围内有效 ============
#include <stdio.h>                      // 标准输入输出
void f1()                               // 函数 f1
{
    int a = 100;                        // a 只在 f1 里有效
    printf("f1 里的 a = %d\n", a);      // 输出 100
}
int main()
{
    int a = 7;                          // main 里的 a,与 f1 的 a 是两个不同的盒子
    f1();                               // 调用 f1
    printf("main 里的 a = %d\n", a);    // 输出 7,互不干扰
    return 0;
}
c
// ============ 13. 全局变量与同名屏蔽(例 7.15)============
#include <stdio.h>                      // 标准输入输出
int a = 3, b = 5;                       // 定义在所有函数之外 —— 全局变量

int max(int p, int q)                   // 形参 p、q 是局部变量
{
    return (p > q) ? p : q;             // 返回两数中的大者
}

int main()
{
    int a = 8;                          // 局部变量 a,把同名的全局变量 a 屏蔽掉
    printf("max= %d\n", max(a, b));     // 实参 a 是局部的 8,b 是全局的 5,结果 8
    return 0;
}
c
// ============ 14. 静态局部变量:只初始化一次(例 7.16)============
#include <stdio.h>                      // 标准输入输出
int f(int a);                           // 函数声明

int main()
{
    int i;
    for (i = 0; i < 3; i++)
        printf("%d\n", f(2));           // 连续三次调用,输出 7 8 9
    return 0;
}

int f(int a)
{
    auto int b = 0;                     // 自动变量:每次进来都重新变成 0
    static int c = 3;                   // 静态局部变量:只在编译时赋一次初值 3
    b = b + 1;                          // b 每次都变 1
    c = c + 1;                          // c 每次都接着上次的值往上加
    return (a + b + c);                 // 返回三者之和
}
c
// ============ 15. auto 与 register(写法演示)============
#include <stdio.h>                      // 标准输入输出
int main()
{
    auto int a = 5;                     // auto 可以省略,写出来只是为了强调它是自动变量
    register int i;                     // 建议把 i 放进 CPU 寄存器(现代编译器会自动优化)
    int sum = 0;                        // 累加用的筐
    for (i = 1; i <= 100; i++)
        sum = sum + a * i;              // 累加 5+10+...+500
    printf("sum = %d\n", sum);          // 输出 25250
    return 0;
}
c
// ============ 16. extern 扩展外部变量的作用域(例 7.18)============
#include <stdio.h>                      // 标准输入输出
int max();                              // 声明 max 函数

int main()
{
    extern int A, B, C;                 // 把定义在后面的全局变量 A、B、C 的作用域扩展到这里
    A = 34;                             // 给全局变量赋值
    B = 67;
    C = 12;
    printf("max is %d\n", max());       // 输出 67
    return 0;
}

int A, B, C;                            // 定义在 main 之后,所以 main 里必须先 extern 声明

int max()
{
    int m;
    m = (A > B) ? A : B;                // 先比 A、B
    if (C > m) m = C;                   // 再与 C 比
    return m;                           // 返回三者最大
}

二、课后题 · 代码题

c
// ============ 习题 1:最大公约数与最小公倍数 ============
#include <stdio.h>                      // 标准输入输出
int gcd(int a, int b)                   // 辗转相除法求最大公约数
{
    int t;                              // 存余数
    while (b != 0)                      // 直到 b 变成 0
    {
        t = a % b;                      // 求余数
        a = b;                          // b 上位
        b = t;                          // 余数上位
    }
    return a;                           // 此时的 a 就是最大公约数
}
int lcm(int a, int b)                   // 最小公倍数
{
    return a * b / gcd(a, b);           // 两数之积除以最大公约数(这里嵌套调用了 gcd)
}
int main()
{
    int m = 12, n = 18;                 // 两个整数
    printf("最大公约数 = %d\n", gcd(m, n));      // 输出 6
    printf("最小公倍数 = %d\n", lcm(m, n));      // 输出 36
    return 0;
}
c
// ============ 习题 3:判断素数 ============
#include <stdio.h>                      // 标准输入输出
#include <math.h>                       // 用到 sqrt
int prime(int n)                        // 是素数返回 1,否则返回 0
{
    int i, k;
    if (n <= 1) return 0;               // 1 和负数都不是素数
    k = (int)sqrt((double)n);           // 只需试除到根号 n
    for (i = 2; i <= k; i++)
        if (n % i == 0) return 0;       // 能整除就不是素数,提前退出
    return 1;                           // 都除不尽,是素数
}
int main()
{
    int n = 97;                         // 待判断的数
    if (prime(n))
        printf("%d 是素数\n", n);       // 97 是素数
    else
        printf("%d 不是素数\n", n);
    return 0;
}
c
// ============ 习题 4:3×3 二维数组转置 ============
#include <stdio.h>                      // 标准输入输出
void convert(int array[3][3])           // 转置:行列互换,不需要返回值
{
    int i, j, t;
    for (i = 0; i < 3; i++)
        for (j = i + 1; j < 3; j++)     // 只走上三角,避免换两次又换回来
        {
            t = array[i][j];            // 三只杯子法
            array[i][j] = array[j][i];
            array[j][i] = t;
        }
}
int main()
{
    int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};   // 原始矩阵
    int i, j;
    convert(a);                         // 二维数组名作实参
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++) printf("%3d", a[i][j]);   // 每个数占 3 列,对齐好看
        printf("\n");
    }
    return 0;
}
c
// ============ 习题 11:起泡法排序 10 个字符 ============
#include <stdio.h>                      // 标准输入输出
void sort(char s[], int n)              // 起泡(冒泡)排序
{
    int i, j;
    char t;
    for (i = 0; i < n - 1; i++)                 // 共 n-1 趟
        for (j = 0; j < n - 1 - i; j++)         // 每趟比 n-1-i 次
            if (s[j] > s[j + 1])                // 前大后小就是逆序
            {
                t = s[j];                       // 交换三件套
                s[j] = s[j + 1];
                s[j + 1] = t;
            }
}
int main()
{
    char a[10] = {'d', 'a', 'f', 'c', 'b', 'e', 'h', 'g', 'j', 'i'};
    int i;
    sort(a, 10);                        // 数组名传地址,排完 a 就变了
    for (i = 0; i < 10; i++) printf("%c ", a[i]);   // 输出 a b c d e f g h i j
    printf("\n");
    return 0;
}
c
// ============ 习题 17:递归把整数转成字符串 ============
#include <stdio.h>                      // 标准输入输出
void convert(int n)                     // 递归输出 n 的每一位
{
    int i;
    if ((i = n / 10) != 0)              // 还有更高的位没处理
        convert(i);                     // 先递归处理前面那一截
    putchar(n % 10 + '0');              // 再输出最后一位(数字转字符要加 '0')
}
int main()
{
    int n = 483;                        // 要转换的整数
    if (n < 0)                          // 处理负数
    {
        putchar('-');
        n = -n;
    }
    convert(n);                         // 输出 483
    putchar('\n');
    return 0;
}

三、我出的题 · 随课练习

🔮 猜输出

c
// ============ 猜输出 1:值传递 ============
#include <stdio.h>                      // 标准输入输出
void swap(int x, int y)                 // 形参是实参的副本
{
    int t;
    t = x;
    x = y;
    y = t;
}
int main()
{
    int a = 3, b = 8;                   // 两个实参
    swap(a, b);                         // 调用后 a、b 会变吗
    printf("%d %d\n", a, b);            // 自己先猜,再运行验证
    return 0;
}
c
// ============ 猜输出 2:static 变量 ============
#include <stdio.h>                      // 标准输入输出
int f(int a)                            // 被调用三次
{
    int b = 0;                          // 自动变量
    static int c = 3;                   // 静态局部变量
    b = b + 1;
    c = c + 1;
    return (a + b + c);                 // 三次分别返回多少
}
int main()
{
    int a = 2, i;
    for (i = 0; i < 3; i++)
        printf("%d ", f(a));            // 猜三个数字
    return 0;
}
c
// ============ 猜输出 3:数组名传地址 ============
#include <stdio.h>                      // 标准输入输出
void change(int a[], int n)             // 形参数组与实参数组共占同一段内存
{
    int i;
    for (i = 0; i < n; i++)
        a[i] = a[i] + 1;                // 修改形参
}
int main()
{
    int b[5] = {1, 2, 3, 4, 5};
    int i;
    change(b, 5);                       // 传首元素地址
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);            // 原数组变了吗
    return 0;
}
c
// ============ 猜输出 4:递归调用次数 ============
#include <stdio.h>                      // 标准输入输出
int f(int n)                            // 递归函数
{
    if (n == 1)                         // 终止条件
        return 1;
    else
        return n * f(n - 1);            // 递归公式
}
int main()
{
    printf("%d\n", f(4) + f(2));        // 两个递归调用相加
    return 0;
}

🛠 变式题

c
// ============ 变式题 1:照猫画虎,补全 min 函数 ============
// 要求:返回 a、b 中较小者。仿照例 7.2 的 max 写法。
// ① 补全下面的函数体(一行 return 就够);
// ② 再自己写一个主函数:定义 x=3、y=8,写一行函数声明,然后 printf 输出 min(x, y),期望 3。

int min(int a, int b)
{
    // 在这里补一行 return
}
c
// ============ 变式题 2:改需求,让 average 支持任意长度 ============
// 说明:例 7.10 的 average 只能处理 10 个元素。
// 请补全形参表和循环上界,使同一个函数能处理任意长度的数组。

float average(float array[], /* 这里补一个长度参数 */)
{
    int i;
    float aver, sum = array[0];
    for (i = 1; i < /* 这里补上界 */; i++)
        sum = sum + array[i];
    aver = sum / /* 这里补除数 */;
    return aver;
}
c
// ============ 变式题 3:改方向,把选择法改成由大到小 ============
// 说明:下面是从小到大的选择法。只改一个比较符号就能变成从大到小。
// 请找出应该改哪一行,并说明为什么。

void sort(int array[], int n)
{
    int i, j, k, t;
    for (i = 0; i < n - 1; i++)
    {
        k = i;
        for (j = i + 1; j < n; j++)
            if (array[j] < array[k])    // ← 这一行要不要改
                k = j;
        t = array[k];
        array[k] = array[i];
        array[i] = t;
    }
}
c
// ============ 变式题 4:补全递归求 1+2+…+n ============
// 要求:sum(5) 返回 15。补全两个空。

int sum(int n)
{
    if (/* 空1:终止条件 */) return 1;
    return /* 空2:递归公式 */;
}

🐛 改错题

c
// ============ 改错题 1:函数定义的首行多了分号 ============
// ❌ 错误示例(故意)
#include <stdio.h>                      // 标准输入输出
int max(int x, int y);                  // ❌ 错:这里多了一个分号,变成了"声明"
{                                       // ❌ 于是这对花括号成了无主的语法错误
    int z;
    z = x > y ? x : y;
    return z;
}
int main()
{
    printf("%d\n", max(3, 5));
    return 0;
}
// 错在哪:定义函数时首行末尾不能加分号。加了分号编译器就认为这是声明,
//         后面那对 { } 失去归属,报语法错误。
// 怎么改:删掉那个分号。
c
// ============ 改错题 2:想用 swap 改实参,却只改了形参 ============
// ❌ 错误示例(故意)
#include <stdio.h>                      // 标准输入输出
void swap(int x, int y)                 // ❌ 错:形参是值传递,改不到实参
{
    int t;
    t = x;
    x = y;
    y = t;
}
int main()
{
    int a = 3, b = 8;
    swap(a, b);                         // 调用后 a、b 仍是 3 和 8
    printf("a=%d, b=%d\n", a, b);
    return 0;
}
// 错在哪:形参和实参是两个不同的存储单元,形参改了实参不会跟着变。
// 怎么改(本章范围内):用全局变量。把 a、b 定义为全局变量,swap 不带参数直接改它们:
//         int a = 3, b = 8;
//         void swap() { int t; t = a; a = b; b = t; }
//         int main() { swap(); printf("a=%d, b=%d\n", a, b); return 0; }
// 更好的办法(学完第 8 章指针再说):swap(int *p, int *q)。
c
// ============ 改错题 3:递归没有终止条件 ============
// ❌ 错误示例(故意)
#include <stdio.h>                      // 标准输入输出
int fac(int n)
{
    return n * fac(n - 1);              // ❌ 错:只有递归公式,没有终止条件
}
int main()
{
    printf("%d\n", fac(5));
    return 0;
}
// 错在哪:n 会一路减到负数,永远不会停,函数一层层套下去,最终栈溢出(stack overflow),
//         程序崩溃。编译能通过,运行才崩。
// 怎么改:加终止条件 if (n == 0 || n == 1) return 1;
//         完整写法:
//         int fac(int n)
//         { if (n == 0 || n == 1) return 1; else return n * fac(n - 1); }

四、加练题

(暂无)