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

第08课 · 要写代码

指针就是地址、& 与 *、指针作函数参数、指针与数组

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

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

一、知识点代码

c
// ============ 1. 取地址运算符 & 与解引用运算符 * ============
#include <stdio.h>                      // 引入标准输入输出,才有 printf
int main()                              // 主函数,程序入口
{
    int a = 100;                        // 建一间 int 房,住进 100,名牌叫 a
    int *p = &a;                        // 造一张 int 便签 p,抄上 a 的房间号(& = 取地址)
    printf("a  = %d\n", a);             // 直接访问:按名字 a 找过去,输出 100
    printf("*p = %d\n", *p);            // 间接访问:照便签上的号去敲门,还是 100
    *p = 200;                           // 解引用后赋值:把 200 送进 p 所指的那间房(就是 a)
    printf("a  = %d\n", a);             // 再按名字看,a 已经变成 200 了
    return 0;                           // 程序正常结束
}                                       // main 结束
c
// ============ 2. 基类型:int * 与 char * 走一步各跨多少 ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int  a = 65;                        // 一间 int 房,住 65
    char c = 'A';                       // 一间 char 房,住字符 'A'
    int  *pi = &a;                      // pi 的基类型是 int,只该指向 int 房
    char *pc = &c;                      // pc 的基类型是 char,只该指向 char 房
    printf("pi   = %p\n", (void *)pi);              // 用 %p 输出 pi 里存的地址
    printf("pc   = %p\n", (void *)pc);              // 用 %p 输出 pc 里存的地址
    printf("pi+1 = %p\n", (void *)(pi + 1));        // int 指针加 1:地址加 4 字节
    printf("pc+1 = %p\n", (void *)(pc + 1));        // char 指针加 1:地址加 1 字节
    printf("%d %c\n", *pi, *pc);        // 解引用取回 65 和 'A'
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 3. 指针作函数参数:真正交换两个变量 ============
#include <stdio.h>                      // 引入 printf
void swap(int *p1, int *p2)             // 形参是两个 int 指针,用来接收两个地址
{
    int temp;                           // 中转站必须是普通 int:要中转的是"人"不是"号"
    temp = *p1;                         // 把 p1 所指房里的值抄进 temp
    *p1  = *p2;                         // 把 p2 所指房里的值搬进 p1 那间
    *p2  = temp;                        // 把 temp 里的值搬进 p2 那间
}                                       // swap 结束
int main()
{
    int a = 3, b = 8;                   // 两个普通变量
    printf("交换前:a = %d, b = %d\n", a, b);   // 输出 3 和 8
    swap(&a, &b);                       // 传进去的是地址,不是值
    printf("交换后:a = %d, b = %d\n", a, b);   // 输出 8 和 3
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 4. 数组名就是首元素的地址 ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int a[5] = {10, 20, 30, 40, 50};    // 五间连号的 int 房
    int *p = a;                         // 等价于 p = &a[0];(数组名本身就是地址)
    printf("a     = %p\n", (void *)a);          // 数组名的值就是一个地址
    printf("&a[0] = %p\n", (void *)&a[0]);      // 与上一行完全相同
    printf("p     = %p\n", (void *)p);          // p 里存的也是这个地址
    printf("%d %d %d\n", a[2], *(a + 2), *(p + 2));   // 三种写法指向同一个元素
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 5. 指针的加减:p+i 与 p2-p1 ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int a[6] = {10, 20, 30, 40, 50, 60};    // 六间连号的 int 房
    int *p = &a[1];                     // p 指向 1 号元素(20)
    int *q = &a[4];                     // q 指向 4 号元素(50)
    printf("*(p + 2) = %d\n", *(p + 2));            // p 往后挪 2 个元素 → 40
    printf("*(q - 1) = %d\n", *(q - 1));            // q 往前挪 1 个元素 → 40
    printf("q - p    = %d\n", (int)(q - p));        // 两指针相减 = 相隔几个元素 → 3
    printf("p+1 跨了 %d 字节\n",                     // 用 char* 相减量出真实字节数
           (int)((char *)(p + 1) - (char *)p));     // 一个 int = 4 字节
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 6. 遍历数组的三种写法 ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int a[5] = {1, 2, 3, 4, 5};         // 五个元素
    int i, *p;                          // i 当循环变量,p 当便签

    for (i = 0; i < 5; i++)             // 写法一:下标法
        printf("%d ", a[i]);            // 直接写 a[i],最直观
    printf("\n");                       // 换行

    for (i = 0; i < 5; i++)             // 写法二:用数组名算地址
        printf("%d ", *(a + i));        // a+i 是地址,交给 * 取值
    printf("\n");                       // 换行

    for (p = a; p < a + 5; p++)         // 写法三:便签从 a[0] 一路走到 a+5
        printf("%d ", *p);              // 取便签当前指着的值
    printf("\n");                       // 换行

    return 0;                           // 结束
}                                       // main 结束
c
// ============ 7. 数组名作函数参数:函数里改,外面跟着变 ============
#include <stdio.h>                      // 引入 printf
void inv(int x[], int n)                // 形参写成数组,编译时按指针处理
{
    int temp, i, j, m = (n - 1) / 2;    // m 是 i 的上界(n=6 时 m=2)
    for (i = 0; i <= m; i++)            // i 从 0 走到中间
    {
        j = n - 1 - i;                  // j 从末尾往中间走
        temp = x[i];                    // 三变量交换的第一步
        x[i] = x[j];                    // 把后面的搬到前面
        x[j] = temp;                    // 把前面的搬到后面
    }                                   // for 结束
}                                       // inv 结束
int main()
{
    int i, a[6] = {1, 2, 3, 4, 5, 6};   // 待逆序的数组
    inv(a, 6);                          // 传数组名 = 传首元素地址
    for (i = 0; i < 6; i++)             // 遍历输出
        printf("%d ", a[i]);            // 结果 6 5 4 3 2 1
    printf("\n");                       // 换行
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 8.(选学)行指针:int (*p)[4] ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int a[3][4] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23};   // 3 行 4 列
    int (*p)[4] = a;                    // p 是行指针,一步跨一行(4 个 int = 16 字节)
    int i, j;                           // i 号行,j 号列
    for (i = 0; i < 3; i++)             // 遍历 3 行
    {
        for (j = 0; j < 4; j++)         // 遍历 4 列
            printf("%3d ", *(*(p + i) + j));   // 先跨 i 行,再跨 j 列,最后取值
        printf("\n");                   // 一行输出完换行
    }                                   // 外层 for 结束
    return 0;                           // 结束
}                                       // main 结束

二、课后题 · 代码题

本章书末习题统一放到第 09 课(讲完 8.4–8.7)后集中做,本区暂时留空。
原因是第 8 章习题会用到字符串指针、函数指针、指针数组,现在做等于硬背。
这一课先把第三区的随课练习吃透——它们和书末习题的题型一一对应。

三、我出的题 · 随课练习

🔮 猜输出

c
// ============ 猜输出 ① ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int a = 10, b = 20;                 // 两个整型变量
    int *p = &a, *q = &b;               // p 指向 a,q 指向 b
    *p = *q;                            // 注意:这里左边带 *,改的是"人"
    p = q;                              // 这里左边不带 *,改的是"号"
    *p = 30;                            // 再用 * 给现在 p 指向的变量赋值
    printf("%d %d %d %d\n", a, b, *p, *q);   // 输出四个值
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 猜输出 ② ============
#include <stdio.h>                      // 引入 printf
void f(int x, int *y)                   // 第一个形参是普通值,第二个是地址
{
    x = x + 1;                          // 改的是复印件(形参 x)
    *y = *y + 1;                        // 改的是 y 指向的那个变量(原件)
}                                       // f 结束
int main()
{
    int a = 1, b = 1;                   // 两个都从 1 开始
    f(a, &b);                           // 传 a 的值,传 b 的地址
    printf("%d %d\n", a, b);            // 看看各自变成了多少
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 猜输出 ③ ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int a[5] = {1, 2, 3, 4, 5};         // 五个元素
    int *p = a;                         // p 指向 a[0]
    printf("%d ", *p++);                // 没有括号:先取还是先挪?
    printf("%d ", *(++p));              // 括号里先算 ++
    printf("%d ", ++(*p));              // 加的是 *p 还是 p?
    printf("%d %d\n", *p, a[2]);        // 最后看看 p 和 a[2]
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 猜输出 ④ ============
#include <stdio.h>                      // 引入 printf
int main()
{
    int a[6] = {10, 20, 30, 40, 50, 60};    // 六个元素
    int *p = &a[1];                     // 指向 1 号元素
    int *q = &a[4];                     // 指向 4 号元素
    printf("%d ", (int)(q - p));        // 两个指针相减,单位是"元素"不是字节
    printf("%d ", *(p + 2));            // 从 p 往后数 2 个元素
    printf("%d\n", *(q - 1));           // 从 q 往前数 1 个元素
    return 0;                           // 结束
}                                       // main 结束

🛠 变式题

c
// ============ 变式题 1:把 swap 改成能交换两个 double ============
// 要求:仿照知识点代码 3,形参用 double *,函数里用 * 交换两个值
#include <stdio.h>                      // 引入 printf / scanf
void swap(double *p1, double *p2)       // 形参是两个 double 指针
{
    // 在这里写你的代码:
    // 定义中转变量,交换 *p1 和 *p2


}                                       // swap 结束
int main()
{
    double x = 1.5, y = 2.5;            // 两个小数
    scanf("%lf,%lf", &x, &y);           // 读入两个小数(用逗号分隔)
    swap(&x, &y);                       // 传地址进去
    printf("%.2f %.2f\n", x, y);        // 输出交换后的值
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 变式题 2:一次带回最大值和最小值 ============
// 要求:补全 maxmin,用两个指针参数把结果带回 main(不许用 return 带值)
#include <stdio.h>                      // 引入 printf
void maxmin(int a[], int n, int *pmax, int *pmin)   // 形参:数组 + 两个地址
{
    // 在这里写你的代码:
    // 1) 先用 a[0] 给 *pmax 和 *pmin 赋初值
    // 2) 循环比较,遇到更大的就更新 *pmax,更小的就更新 *pmin


}                                       // maxmin 结束
int main()
{
    int a[8] = {12, 5, 78, 3, 90, 6, 41, 27};   // 待处理的数组
    int max, min;                       // 用来接收结果的两个变量
    maxmin(a, 8, &max, &min);           // 传数组名 + 两个变量的地址
    printf("max = %d, min = %d\n", max, min);   // 目标:max = 90, min = 3
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 变式题 3:把下标法翻译成指针法 ============
// 要求:不许出现 [],只用指针,结果应输出 186
#include <stdio.h>                      // 引入 printf
int main()
{
    int a[8] = {12, 5, 78, 3, 90, 6, 41, 27};   // 八个元素
    int *p;                             // 一张便签
    int sum = 0;                        // 累加器,从 0 开始
    // 在这里写你的代码:
    // 用 p 从 a 走到 a+8,判断每个元素是否为偶数,是就累加进 sum


    printf("%d\n", sum);                // 正确结果是 186
    return 0;                           // 结束
}                                       // main 结束
c
// ============ 变式题 4:补全 reverse(两个指针对撞) ============
// 要求:形参用指针变量,循环用两个指针 i、j 从两头往中间撞,不许用 []
#include <stdio.h>                      // 引入 printf
void reverse(int *x, int n)             // 形参是指针变量
{
    // 在这里写你的代码:
    // int *i = x;                 // i 指向头
    // int *j = x + n - 1;         // j 指向尾
    // while (i < j) { 交换 *i 和 *j; i++; j--; }


}                                       // reverse 结束
int main()
{
    int a[6] = {1, 2, 3, 4, 5, 6};      // 待逆序的数组
    int i;                              // 输出用的循环变量
    reverse(a, 6);                      // 传数组名
    for (i = 0; i < 6; i++)             // 遍历输出
        printf("%d ", a[i]);            // 目标输出:6 5 4 3 2 1
    printf("\n");                       // 换行
    return 0;                           // 结束
}                                       // main 结束

🐛 改错题

c
// ============ 改错题 1 ============
#include <stdio.h>                      // 引入 printf
int main()
{
    // ❌ 错误示例(故意)
    int *p;                             // 只造了便签,没写房号 → 野指针
    *p = 100;                           // 照着空号去敲门,往未知单元写数据
    printf("%d\n", *p);                 // 输出什么完全不可预料
    return 0;                           // 结束
}                                       // main 结束
// 【错在哪】p 没有确定值,*p 指向的存储单元不可预料,写它可能破坏别的数据。
// 【怎么改】先让 p 有确定指向:
//           int a;
//           int *p = &a;
//           *p = 100;
c
// ============ 改错题 2 ============
#include <stdio.h>                      // 引入 printf
int main()
{
    // ❌ 错误示例(故意)
    int a = 5;                          // 一间 int 房,住 5
    int *p;                             // 一张空白便签
    *p = &a;                            // 错:左边是"p 指向的变量",不是 p 本身
    printf("%d\n", *p);                 // 程序行为不可预料
    return 0;                           // 结束
}                                       // main 结束
// 【错在哪】*p 的类型是 int,&a 的类型是 int *,两边类型对不上;
//           而且此时 p 还没有确定值,*p 指向哪里都说不清。
// 【怎么改】去掉左边的 *:p = &a;
c
// ============ 改错题 3 ============
#include <stdio.h>                      // 引入 printf
void swap(int *p1, int *p2)             // 两个指针形参
{
    // ❌ 错误示例(故意)
    int *t;                             // 想用指针当中转站
    t = p1;                             // 这里换的是"号",不是"人"
    p1 = p2;                            // p1 现在指向 b
    p2 = t;                             // p2 现在指向 a
}                                       // swap 结束
int main()
{
    int a = 3, b = 8;                   // 两个变量
    swap(&a, &b);                       // 传地址
    printf("%d %d\n", a, b);            // 期望 8 3,实际仍是 3 8
    return 0;                           // 结束
}                                       // main 结束
// 【错在哪】函数里只换了形参指针本身,实参 a、b 一个都没动。
//           指针形参也是"值传递",函数里改复印件,原件不变。
// 【怎么改】交换指针所指的值,中转变量用普通 int:
//           int t;
//           t = *p1;  *p1 = *p2;  *p2 = t;

四、加练题

(暂无)