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

第09课 · 要写代码

字符指针、函数指针、指针数组、动态内存 malloc/free、第8章习题

25 段代码每行都有注释边敲边想
教材依据第8章《善于利用指针》书页 p.217–292
本课覆盖:教材第8章正文 + 书末习题(p.291–292)。标「教材 p.X」= 教材段落总结;「例题」= 教材例题;「习题」= 书末题目;「本课补充」= 非教材原文。
在全书对照表中查看 →

本文件只干一件事:把第 09 课的每个语法点,写成能直接编译运行的完整程序,每行都带 // 注释。
用法:先照着敲一遍 → 编译运行 → 再合上书自己默写 → 最后做第三区的题。

一、知识点代码

c
// ============ 1. 字符数组:自己包下一整排房 ============
#include <stdio.h>                     // 引入 printf
int main()                             // 主函数,程序入口
{
    char string[] = "I love China!";   // 定义字符数组;13 个字符 + 1 个 '\0',长度自动为 14
    printf("%s\n", string);            // %s 从首元素地址开始输出,遇 '\0' 停
    printf("%c\n", string[7]);         // 下标法:第 8 个字符 'C'(下标从 0 起)
    printf("%c\n", *(string + 7));     // 地址法:与 string[7] 完全等价
    return 0;                          // 程序正常结束
}                                      // main 结束
c
// ============ 2. 字符指针:便签上只写首字符的房间号 ============
#include <stdio.h>                     // 引入 printf
int main()
{
    char *string = "I love China!";    // 把字符串第 1 个字符的地址存进指针
    printf("%s\n", string);            // 从 string 指向处输出到 '\0'
    string = "I am a student.";        // 指针可以改指向(数组名不行)
    printf("%s\n", string);            // 输出新字符串
    return 0;
}                                      // main 结束
c
// ============ 3. 用指针变量复制字符串(例 8.19) ============
#include <stdio.h>                     // 引入 printf
int main()
{
    char a[] = "I am a boy.", b[20], *p1, *p2;  // a 存原串,b 留 20 格,p1/p2 是便签
    p1 = a;                            // p1 指向 a[0]
    p2 = b;                            // p2 指向 b[0]
    for (; *p1 != '\0'; p1++, p2++)    // 没到 '\0' 就继续;每轮两个指针都加 1
        *p2 = *p1;                     // 把 p1 指的字符抄到 p2 指的位置
    *p2 = '\0';                        // 复制完补字符串结束标志
    printf("string a is: %s\n", a);    // 输出原串
    printf("string b is: %s\n", b);    // 输出复制结果
    return 0;
}                                      // main 结束
c
// ============ 4. 字符指针作函数参数:两种写法 ============
#include <stdio.h>                     // 引入 printf
void copy1(char *from, char *to)       // 形参是字符指针
{
    for (; *from != '\0'; from++, to++) // 顺着 from 走
        *to = *from;                   // 一个字符一个字符地抄
    *to = '\0';                        // 补句号
}                                      // copy1 结束
void copy2(char *from, char *to)       // 教材的精简版
{
    while ((*to++ = *from++) != '\0')  // 先赋值,再用赋值结果和 '\0' 比较,然后各自加 1
        ;                              // 循环体是空的,活都在表达式里干完了
}                                      // copy2 结束
int main()
{
    char a[] = "I am a teacher.";      // 源串
    char b[] = "You are a student.";   // 目标串(比源串长)
    char c[] = "You are a student.";   // 第二个目标串
    copy1(a, b);                       // 传数组名 = 传首地址
    printf("copy1: %s\n", b);          // 输出被改过的 b
    copy2(a, c);                       // 用精简版再复制一次
    printf("copy2: %s\n", c);          // 结果一样
    return 0;
}                                      // main 结束
c
// ============ 5. 函数指针:便签上写的是窗口号 ============
#include <stdio.h>                     // 引入 printf
int max(int x, int y)                  // 求大者
{
    return x > y ? x : y;              // 返回较大的那个
}                                      // max 结束
int min(int x, int y)                  // 求小者
{
    return x < y ? x : y;              // 返回较小的那个
}                                      // min 结束
int main()
{
    int (*p)(int, int);                // 定义指向函数的指针变量 p
    int a = 34, b = 89, c;             // 两个操作数和一个结果
    p = max;                           // p 指向 max(只写函数名,不写括号)
    c = (*p)(a, b);                    // 通过 p 调用 max
    printf("max = %d\n", c);           // 输出 89
    p = min;                           // 同一个 p 改指 min
    c = (*p)(a, b);                    // 通过 p 调用 min
    printf("min = %d\n", c);           // 输出 34
    return 0;
}                                      // main 结束
c
// ============ 6. 函数指针作函数参数(回调的雏形) ============
#include <stdio.h>                     // 引入 printf
int max(int x, int y) { return x > y ? x : y; }   // 求大者
int min(int x, int y) { return x < y ? x : y; }   // 求小者
int fun(int x, int y, int (*p)(int, int))         // 第三个形参是函数指针
{
    return (*p)(x, y);                 // 调谁?看实参传的是哪个函数名
}                                      // fun 结束
int main()
{
    printf("%d\n", fun(3, 5, max));    // 传 max 的入口地址 → 5
    printf("%d\n", fun(3, 5, min));    // 传 min 的入口地址 → 3
    return 0;
}                                      // main 结束
c
// ============ 7. 返回指针值的函数:问询处递给你一张便签 ============
#include <stdio.h>                     // 引入 printf
float *search(float (*pointer)[4], int n)   // 返回值是 float *;形参是行指针
{
    float *pt;                         // 准备一张指向 float 的便签
    pt = *(pointer + n);               // 先移行,再取该行第 0 个元素的地址
    return pt;                         // 返回 &score[n][0]
}                                      // search 结束
int main()
{
    float score[][4] = {{60, 70, 80, 90},   // 第 0 个学生
                        {56, 89, 67, 88},   // 第 1 个学生
                        {34, 78, 90, 66}};  // 第 2 个学生
    float *p;                          // 接返回值的便签
    int i;                             // 循环变量
    p = search(score, 1);              // 取第 1 个学生的成绩地址
    for (i = 0; i < 4; i++)            // 4 门课
        printf("%5.2f ", *(p + i));    // 依次输出
    printf("\n");                      // 换行
    return 0;
}                                      // main 结束
c
// ============ 8. 指针数组:只换名片,不搬行李 ============
#include <stdio.h>                     // 引入 printf
#include <string.h>                    // 引入 strcmp
void sort(char *name[], int n)         // 形参是指针数组名
{
    char *temp;                        // 暂存一张名片
    int i, j, k;                       // 循环变量
    for (i = 0; i < n - 1; i++)        // 选择法外层
    {
        k = i;                         // 假定第 i 个最小
        for (j = i + 1; j < n; j++)    // 在内层找更小的
            if (strcmp(name[k], name[j]) > 0)  // 字典序比较
                k = j;                 // 记下更小的下标
        if (k != i)                    // 最小值不在第 i 位
        {
            temp = name[i];            // 暂存第 i 张名片
            name[i] = name[k];         // 换名片
            name[k] = temp;            // 换回来
        }                              // if 结束
    }                                  // for 结束
}                                      // sort 结束
int main()
{
    char *name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"};  // 5 张名片
    int i;                             // 循环变量
    sort(name, 5);                     // 排序
    for (i = 0; i < 5; i++)            // 按新顺序输出
        printf("%s\n", name[i]);       // 输出第 i 张名片指向的字符串
    return 0;
}                                      // main 结束
c
// ============ 9. 二级指针:保险柜里放便签 ============
#include <stdio.h>                     // 引入 printf
int main()
{
    char *name[] = {"Follow me", "BASIC", "Great Wall"};  // 指针数组(3 张名片)
    char **p;                          // 二级指针:指向"名片"的便签
    int i;                             // 循环变量
    for (i = 0; i < 3; i++)            // 走遍 3 张名片
    {
        p = name + i;                  // p 指向第 i 张名片
        printf("%s\n", *p);            // *p 就是 name[i],一个地址
    }                                  // for 结束
    p = name + 2;                      // p 指向第 3 张名片
    printf("%c\n", **p);               // **p 才是字符串的第 1 个字符 'G'
    return 0;
}                                      // main 结束
c
// ============ 10. malloc 动态开房 + free 退房 ============
#include <stdio.h>                     // 引入 printf/scanf
#include <stdlib.h>                    // 引入 malloc/free
int main()
{
    int *p;                            // 将来指向动态数组的便签
    int i;                             // 循环变量
    p = (int *)malloc(5 * sizeof(int)); // 开 5 个 int 的空间,把 void * 转成 int *
    if (p == NULL)                     // 申请失败必须检查
    {
        printf("malloc failed\n");     // 提示失败
        return 1;                      // 非 0 表示异常结束
    }                                  // if 结束
    for (i = 0; i < 5; i++)            // 输入 5 个数
        scanf("%d", p + i);            // p+i 就是 &p[i]
    for (i = 0; i < 5; i++)            // 输出 5 个数
        printf("%d ", *(p + i));       // 按地址取值
    printf("\n");                      // 换行
    free(p);                           // 退房,把空间还给系统
    p = NULL;                          // 房卡作废,防止野指针
    return 0;
}                                      // main 结束
c
// ============ 11. void *:只有房间号,没有房型 ============
#include <stdio.h>                     // 引入 printf
int main()
{
    int a = 65;                        // 一间 int 房,住 65
    int *p1 = &a;                      // p1 指向 int
    void *p3;                          // 无类型指针:纯地址
    p3 = (void *)p1;                   // int * 转成 void *
    printf("%d\n", *(int *)p3);        // 用之前先转回 int *,才能取值
    return 0;
}                                      // main 结束

二、课后题 · 代码题

c
// ============ 课后题 2:3 个字符串排序(交换指针) ============
#include <stdio.h>                     // 引入 printf/scanf
#include <string.h>                    // 引入 strcmp
void swap(char **p1, char **p2)        // 二级指针:接收"指针的地址"
{
    char *t = *p1;                     // 暂存第一张名片
    *p1 = *p2;                         // 第二张放进第一个位置
    *p2 = t;                           // 暂存放进第二个位置
}                                      // swap 结束
int main()
{
    char s1[50], s2[50], s3[50];       // 三个字符数组存字符串
    char *p[3];                        // 三张名片
    scanf("%49s%49s%49s", s1, s2, s3); // 输入三个字符串
    p[0] = s1; p[1] = s2; p[2] = s3;   // 名片分别指向三个数组
    if (strcmp(p[0], p[1]) > 0) swap(&p[0], &p[1]);  // 前两个逆序就换
    if (strcmp(p[0], p[2]) > 0) swap(&p[0], &p[2]);  // 让最小者到 p[0]
    if (strcmp(p[1], p[2]) > 0) swap(&p[1], &p[2]);  // 后两个排序
    printf("%s\n%s\n%s\n", p[0], p[1], p[2]);        // 按顺序输出
    return 0;
}                                      // main 结束
c
// ============ 课后题 5:约瑟夫环 ============
#include <stdio.h>                     // 引入 printf/scanf
int main()
{
    int a[100];                        // 最多 100 个人
    int n, i, count = 0, left;         // n 人数,count 报数,left 剩余人数
    int *p = a;                        // p 指向数组首元素
    scanf("%d", &n);                   // 输入人数
    for (i = 0; i < n; i++)            // 初始化
        *(p + i) = 1;                  // 1 表示还在圈里
    left = n;                          // 开始时有 n 人
    i = 0;                             // 从第 0 号开始
    while (left > 1)                   // 只剩 1 人时停
    {
        if (*(p + i))                  // 还在圈里的人才报数
        {
            count++;                   // 报数加 1
            if (count == 3)            // 报到 3
            {
                *(p + i) = 0;          // 退出圈子
                left--;                // 剩余人数减 1
                count = 0;             // 重新报数
            }                          // if 结束
        }                              // if 结束
        i = (i + 1) % n;               // 走到末尾绕回开头
    }                                  // while 结束
    for (i = 0; i < n; i++)            // 找最后留下的人
        if (*(p + i))                  // 标记还是 1
            printf("最后留下的是原来第%d号\n", i + 1);  // 人是 1 号起
    return 0;
}                                      // main 结束
c
// ============ 课后题 13:矩形法求定积分(函数指针作参数) ============
#include <stdio.h>                     // 引入 printf
#include <math.h>                      // 引入 sin/cos/exp
double integral(double a, double b, double (*fun)(double))  // 通用积分函数
{
    int n = 10000, i;                  // 分成 10000 个小矩形
    double h = (b - a) / n;            // 每个小矩形的宽
    double s = 0.0;                    // 面积累加器
    for (i = 0; i < n; i++)            // 逐个矩形累加
        s += fun(a + (i + 0.5) * h) * h;   // 中点高度 × 宽
    return s;                          // 返回积分值
}                                      // integral 结束
double f1(double x) { return sin(x); } // 被积函数 1
double f2(double x) { return cos(x); } // 被积函数 2
double f3(double x) { return exp(x); } // 被积函数 3
int main()
{
    printf("sin: %f\n", integral(0.0, 1.0, f1));  // 传 f1 的入口地址
    printf("cos: %f\n", integral(0.0, 1.0, f2));  // 传 f2 的入口地址
    printf("exp: %f\n", integral(0.0, 1.0, f3));  // 传 f3 的入口地址
    return 0;
}                                      // main 结束
c
// ============ 课后题 21:二级指针给 n 个整数排序 ============
#include <stdio.h>                     // 引入 printf/scanf
#include <stdlib.h>                    // 引入 malloc/free
void sort(int **p, int n)              // p 指向"指针数组"
{
    int i, j, *t;                      // 循环变量和临时指针
    for (i = 0; i < n - 1; i++)        // 选择法外层
        for (j = i + 1; j < n; j++)    // 内层
            if (**(p + i) > **(p + j)) // 比较指针最终指向的整数
            {
                t = *(p + i);          // 暂存一个指针
                *(p + i) = *(p + j);   // 交换指针
                *(p + j) = t;          // 换回来
            }                          // if 结束
}                                      // sort 结束
int main()
{
    int n, i;                          // n 个数
    int *a, **p;                       // a 存数据,p 存指针
    scanf("%d", &n);                   // 输入个数
    a = (int *)malloc(n * sizeof(int));      // 开数据空间
    p = (int **)malloc(n * sizeof(int *));   // 开指针空间
    if (a == NULL || p == NULL) return 1;    // 申请失败就退出
    for (i = 0; i < n; i++)            // 输入并建立指针
    {
        scanf("%d", a + i);            // 读入第 i 个数
        *(p + i) = a + i;              // 第 i 个指针指向第 i 个数
    }                                  // for 结束
    sort(p, n);                        // 排序
    for (i = 0; i < n; i++)            // 输出
        printf("%d ", **(p + i));      // 二次解引用拿到整数
    printf("\n");                      // 换行
    free(p);                           // 释放指针数组
    free(a);                           // 释放数据数组
    return 0;
}                                      // main 结束

三、我出的题 · 随课练习

🔮 猜输出

c
// ============ 猜输出 1:指针走字符串 ============
#include <stdio.h>                     // 引入 printf
int main()
{
    char s[] = "abcdef";               // 字符数组,内容可改
    char *p = s;                       // p 指向 s[0]
    printf("%c\n", *(p + 2));          // 从 p 往后走 2 个字符,输出一个字符
    printf("%s\n", p + 3);             // 从 p 往后走 3 个字符,输出到 '\0'
    printf("%c\n", p[5]);              // 用下标形式取一个字符
    p += 2;                            // 便签改指,往后挪 2 个字符
    printf("%s\n", p);                 // 从新位置输出到 '\0'
    return 0;
}                                      // main 结束
c
// ============ 猜输出 2:函数指针换窗口 ============
#include <stdio.h>                     // 引入 printf
int add(int x, int y) { return x + y; }   // 加法窗口
int mul(int x, int y) { return x * y; }   // 乘法窗口
int main()
{
    int (*p)(int, int) = add;          // p 先指向 add
    printf("%d\n", (*p)(3, 4));        // 通过 p 调用
    p = mul;                           // p 改指 mul
    printf("%d\n", p(3, 4));           // 用 p 直接调用
    return 0;
}                                      // main 结束
c
// ============ 猜输出 3:指针数组与二级指针 ============
#include <stdio.h>                     // 引入 printf
int main()
{
    char *name[] = {"Follow me", "BASIC", "Great Wall"};  // 3 张名片
    char **p = name;                   // p 指向第 1 张名片
    printf("%s\n", *(p + 1));          // 取第 2 张名片指向的字符串
    printf("%c\n", **(p + 2));         // 取第 3 个字符串的首字符
    printf("%s\n", *p + 7);            // 第 1 个字符串的地址往后挪 7 个字符
    return 0;
}                                      // main 结束
c
// ============ 猜输出 4:动态数组的指针运算 ============
#include <stdio.h>                     // 引入 printf
#include <stdlib.h>                    // 引入 malloc/free
int main()
{
    int *p = (int *)malloc(3 * sizeof(int));  // 开 3 个 int
    int *q;                            // 另一张便签
    if (p == NULL) return 1;           // 申请失败就退出
    p[0] = 10; p[1] = 20; p[2] = 30;   // 给三个元素赋值
    q = p;                             // q 和 p 指向同一块空间
    q++;                               // q 往后挪一个元素
    printf("%d %d %d\n", *p, *q, *(p + 2));   // 输出三个值
    printf("%d\n", (int)(q - p));      // 两个指针相减
    free(p);                           // 释放
    p = NULL;                          // 置空
    return 0;
}                                      // main 结束

🛠 变式题

c
// ============ 变式 1:只复制大写字母(补全 copy_upper) ============
void copy_upper(char *to, char *from)  // 从 from 复制大写字母到 to
{
    // TODO: 用 while 遍历 from;如果是 'A'~'Z',就 *to++ = *from;
    // TODO: 循环结束后别忘了 *to = '\0';
    // 提示:可以用 while (*from != '\0') { ... from++; }
}
c
// ============ 变式 2:函数指针数组做计算器(补全 main) ============
int add(int x, int y) { return x + y; }   // 加法
int sub(int x, int y) { return x - y; }   // 减法
int mul(int x, int y) { return x * y; }   // 乘法
// TODO: 再写一个 div 函数(注意除数为 0 的处理)
// TODO: 在 main 里定义 int (*p[4])(int,int) = {add, sub, mul, div};
// TODO: 读入两个数和运算符序号,用 (*p[n-1])(a,b) 调用
c
// ============ 变式 3:按字符串长度排序(补全 sort_len) ============
// 提示:这个函数要用 strlen,真正写程序时文件开头要包含 string.h 头文件
void sort_len(char *p[], int n)        // p 是指针数组
{
    // TODO: 用选择法或冒泡法,比较 strlen(p[i]) 与 strlen(p[j])
    // TODO: 交换的是指针 p[i] 和 p[j],不是字符串内容
}

🐛 改错题

c
// ❌ 错误示例(故意)
#include <stdio.h>                     // 引入 printf/scanf
int main()
{
    char *p;                           // 只定义了一张空便签,没写房间号
    scanf("%s", p);                    // 错:野指针,往未知地址写数据
    printf("%s\n", p);                 // 错:读的也是未知地址
    return 0;
}
// 错在哪:p 没有指向任何有效空间。
// 怎么改:char *p, str[100]; p = str; scanf("%s", p);
c
// ❌ 错误示例(故意)
#include <stdio.h>                     // 引入 printf
int main()
{
    char str[20];                      // 定义字符数组
    str = "hello";                     // 错:数组名是地址常量,不能整体赋值
    printf("%s\n", str);               // 输出
    return 0;
}
// 错在哪:C 不允许用赋值语句给字符数组整体赋值。
// 怎么改:定义时初始化 char str[20] = "hello"; 或用 strcpy(str, "hello");
c
// ❌ 错误示例(故意)
#include <stdio.h>                     // 引入 printf
#include <stdlib.h>                    // 引入 malloc/free
int main()
{
    int *p = (int *)malloc(5 * sizeof(int));  // 开 5 个 int
    int i;                             // 循环变量
    if (p == NULL) return 1;           // 检查申请结果
    for (i = 0; i < 5; i++) p[i] = i;  // 赋值
    free(p);                           // 退房
    printf("%d\n", p[0]);              // 错:已经退房还进去拿东西
    return 0;
}
// 错在哪:free 之后 p 指向的空间已经还给系统,不能再访问(释放后使用)。
// 怎么改:free(p); p = NULL; 并且把 printf 移到 free 之前。

四、加练题

(暂无。想加练就说"再出几道 XX 题",我会标日期追加到这里。)