本章的每一个语法点都配一段完整可编译的程序。每行代码后面跟
//注释,解释这一行在干什么。
建议用法:先照着敲一遍 → 编译运行 → 合上答案自己默写 → 再对照。
一、知识点代码
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); }四、加练题
(暂无)