本文件只干一件事:把第 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 题",我会标日期追加到这里。)