由于不同版本的教材(如谭浩强、C Primer Plus、K&R等)的课后习题不同,我无法提供某个特定教材的“标准答案”,我可以为您提供一系列经典、典型的C语言课后习题,并附上详细的解析、代码和核心知识点讲解,这足以帮助您应对大部分教材的类似题目,并真正掌握C语言编程。

C语言程序设计课后习题及答案详解
第一章:C语言概述
本章目标: 了解C语言的历史、特点、基本结构,以及如何编写、编译和运行一个简单的C程序。
第二章:数据类型、运算符与表达式
本章目标: 掌握C语言的基本数据类型、各种运算符的使用以及表达式的构建和求值。
习题1:变量赋值与基本运算
**
编写一个程序,定义两个整型变量 a 和 b,并分别赋值为10和5,然后计算并输出它们的和、差、积、商和余数。
答案与解析:

#include <stdio.h> // 引入标准输入输出库,用于 printf 函数
int main() {
// 1. 定义变量并初始化
int a = 10;
int b = 5;
// 2. 进行算术运算
int sum = a + b;
int difference = a - b;
int product = a * b;
int quotient = a / b; // 整数除法,结果为整数
int remainder = a % b; // 取模运算,得到余数
// 3. 使用 printf 函数输出结果
// %d 是格式化占位符,用于输出整型变量
printf("a = %d, b = %d\n", a, b);
printf("和: %d\n", sum);
printf("差: %d\n", difference);
printf("积: %d\n", product);
printf("商: %d\n", quotient);
printf("余数: %d\n", remainder);
return 0; // 程序正常结束,返回0
}
核心知识点:
- 变量定义与初始化:
int a = 10;定义了一个整型变量a并赋初值10。 - 基本算术运算符:
- (加法)
- (减法)
- (乘法)
- (除法):注意,如果两个操作数都是整数, 执行的是整数除法,结果会舍弃小数部分。
- (取模/求余):用于求两个整数相除后的余数。
printf函数: 用于格式化输出。\n是一个转义字符,表示换行。main函数: C程序的入口点。return 0;: 表示程序成功执行完毕,并向操作系统返回0。
习题2:数据类型转换
**
定义一个整型变量 a = 5,一个浮点型变量 b = 2.0,计算 a / b 和 a / (int)b 的值,并分析结果不同的原因。
答案与解析:
#include <stdio.h>
int main() {
int a = 5;
float b = 2.0f; // 2.0f 表示这是一个 float 类型常量
// 情况一:a / b
// int 类型除以 float 类型,系统会自动将 int a 提升为 float 类型
// 然后进行浮点数除法
float result1 = a / b;
printf("a / b 的结果是: %f\n", result1); // 输出 2.500000
// 情况二:a / (int)b
// (int)b 是一个强制类型转换,将浮点数 b 强制转换为整数 2
// 然后进行 int / int 的整数除法
int result2 = a / (int)b;
printf("a / (int)b 的结果是: %d\n", result2); // 输出 2
return 0;
}
核心知识点:

- 自动类型转换(隐式转换): 当参与运算的操作数类型不同时,系统会自动将“较低”类型的数据转换为“较高”类型的数据(
int->float->double),然后再进行运算。 - 强制类型转换(显式转换): 使用
(类型名)可以将一个变量或表达式的值临时转换为指定的类型。(int)b会将b的值从0f变为2,然后参与运算。 - 运算符优先级: 强制类型转换的优先级高于除法运算符。
习题3:自增自减运算符
** 分析以下程序的输出结果,并解释原因。
#include <stdio.h>
int main() {
int a = 1, b = 1;
int c, d;
c = a++; // 后缀自增
d = ++b; // 前缀自增
printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);
return 0;
}
答案与解析:
输出结果:
a = 2, b = 2, c = 1, d = 2
原因分析:
- 后缀自增 (
a++): 先使用,后增加。c = a++;这条语句的执行过程是:- 将
a的当前值(1)赋给c,c的值是1。 a的值自增1,变为2。
- 将
- 前缀自增 (
++b): 先增加,后使用。d = ++b;这条语句的执行过程是:- 先将
b的值自增1,变为2。 - 将
b的新值(2)赋给d,d的值是2。
- 先将
核心知识点:
- 自增/自减运算符: (自增), (自减)。
- 前缀 vs 后缀: 这是C语言中一个非常容易混淆但非常重要的概念,前缀是“先操作,后使用”,后缀是“先使用,后操作”。
第三章:顺序、选择与循环结构
本章目标: 掌握C语言的三种基本程序结构,特别是条件判断和循环控制。
习题1:if-else 判断
** 编写一个程序,从键盘输入一个整数,判断它是奇数还是偶数。
答案与解析:
#include <stdio.h>
int main() {
int num;
// 提示用户输入
printf("请输入一个整数: ");
// 从键盘读取一个整数,存入 num 变量
scanf("%d", &num);
// 使用 if-else 结构进行判断
// 取模运算 % 用于判断是否能被2整除
if (num % 2 == 0) {
// 如果条件为真 (num % 2 的结果等于 0)
printf("%d 是偶数,\n", num);
} else {
// 如果条件为假
printf("%d 是奇数,\n", num);
}
return 0;
}
核心知识点:
scanf函数: 用于从标准输入(键盘)读取数据。%d表示读取一个整数,&num是num变量的地址,scanf需要知道把数据存放在哪里。if-else语句: 基本的条件判断结构。- 关系运算符: (等于), (不等于),
>(大于),<(小于),>=(大于等于),<=(小于等于)。注意 是赋值, 是判断是否相等。 - 逻辑表达式:
num % 2 == 0是一个逻辑表达式,其结果为真(1)或假(0)。
习题2:switch-case 结构
** 编写一个程序,根据输入的数字(1-7),输出对应的星期几,如果输入不在1-7范围内,则提示“输入错误”。
答案与解析:
#include <stdio.h>
int main() {
int day;
printf("请输入一个数字 (1-7): ");
scanf("%d", &day);
// 使用 switch-case 结构
switch (day) {
case 1:
printf("星期一\n");
break; // break 语句用于跳出 switch 结构
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期日\n");
break;
default: // 如果所有 case 都不匹配,则执行 default
printf("输入错误!请输入1到7之间的数字,\n");
break; // 虽然 default 后的 break 不是必须的,但加上是好习惯
}
return 0;
}
核心知识点:
switch-case语句: 适用于多分支情况,基于一个表达式的值进行匹配。break语句: 在switch中,break用于终止switch语句的执行,如果没有break,程序会继续执行下一个case(称为“case穿透”)。default分支: 当switch表达式的值与所有case的值都不匹配时,执行default分支。
习题3:for 循环
** 计算1到100之间所有整数的和。
答案与解析:
#include <stdio.h>
int main() {
int sum = 0; // 用于存放累加和,初始值必须为0
int i; // 循环变量
// for 循环结构
// 初始化部分: i = 1
// 循环条件: i <= 100 (当条件为真时,执行循环体)
// 增量部分: i++ (每次循环结束后执行)
for (i = 1; i <= 100; i++) {
sum = sum + i; // 或者使用简写: sum += i;
}
printf("1到100的和是: %d\n", sum);
return 0;
}
核心知识点:
for循环: 最常用的循环结构,特别适合已知循环次数的情况。- 循环三要素: 初始化、循环条件、增量/减量。
- 累加器:
sum变量是一个典型的累加器,用于在循环中不断累加数值。
习题4:while 循环
**
使用 while 循环,计算 10 的阶乘(10!)。
答案与解析:
#include <stdio.h>
int main() {
int n = 10;
long long result = 1; // 阶乘增长很快,使用 long long 防止溢出
int i = 1;
// while 循环
// 先判断条件,如果为真,则执行循环体
while (i <= n) {
result = result * i; // 或者 result *= i;
i++; // 循环变量必须更新,否则会陷入死循环
}
printf("%d! = %lld\n", n, result);
return 0;
}
核心知识点:
while循环: “当型”循环,先判断条件,后执行循环体。- 循环变量更新: 在
while循环中,循环变量的更新(如i++)必须由程序员在循环体内部完成,否则极易造成“死循环”。 - 数据类型选择: 计算阶乘时,结果会非常大。
int类型很容易溢出,因此应选择范围更大的数据类型,如long或long long。printf中%lld用于输出long long类型。
第四章:数组
本章目标: 掌握一维数组和二维数组的定义、初始化、引用和基本操作(如排序、查找)。
习题1:一维数组与冒泡排序
** 定义一个包含10个整数的数组,使用冒泡排序算法将其按从小到大的顺序排序,并输出排序前后的数组。
答案与解析:
#include <stdio.h>
#define N 10 // 定义一个常量,表示数组大小
int main() {
int arr[N] = {64, 34, 25, 12, 22, 11, 90, 88, 76, 50};
int i, j, temp;
// --- 输出排序前的数组 ---
printf("排序前的数组: ");
for (i = 0; i < N; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// --- 冒泡排序 ---
// 外层循环控制排序的轮数
for (i = 0; i < N - 1; i++) {
// 内层循环负责每轮比较和交换
// 每一轮结束后,最大的元素会“冒泡”到数组末尾,所以内层循环可以减少 i 次
for (j = 0; j < N - 1 - i; j++) {
// 如果前一个元素比后一个元素大,则交换它们
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
// --- 输出排序后的数组 ---
printf("排序后的数组: ");
for (i = 0; i < N; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
核心知识点:
- 数组定义与初始化:
int arr[10] = {...};定义并初始化一个整型数组。 - 数组遍历: 使用
for循环配合下标(从0开始)来访问数组中的每一个元素。 - 冒泡排序算法: 一种简单的排序算法,核心思想是重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。
- 变量交换: 通常需要一个临时变量
temp来完成两个变量的值交换。
习题2:二维数组
** 一个3x3的矩阵,求其主对角线元素之和。
答案与解析:
#include <stdio.h>
#define ROWS 3
#define COLS 3
int main() {
int matrix[ROWS][COLS] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int sum = 0;
int i, j;
printf("矩阵为:\n");
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLS; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// 求主对角线元素之和
// 主对角线元素的行下标和列下标相等
for (i = 0; i < ROWS; i++) {
sum += matrix[i][i];
}
printf("主对角线元素之和为: %d\n", sum);
return 0;
}
核心知识点:
- 二维数组定义与初始化:
int matrix[3][3] = {{...}, {...}, {...}};。 - 二维数组访问: 使用
matrix[行下标][列下标]的形式访问元素,下标均从0开始。 - 主对角线: 在一个方阵中,主对角线上的元素满足
行下标 == 列下标。
第五章:函数
本章目标: 理解函数的概念,掌握函数的定义、声明、调用,以及参数传递(值传递)和返回值。
习题1:函数的定义、声明与调用
**
编写一个函数 isPrime(int num),用于判断一个整数是否为素数(质数),在 main 函数中调用该函数,并测试几个数字。
答案与解析:
#include <stdio.h>
#include <stdbool.h> // 引入 bool, true, false
// 函数声明(可以放在 main 函数之前,也可以放在 main 函数之后但在 main 函数内部声明)
bool isPrime(int num);
int main() {
int test_num1 = 17;
int test_num2 = 15;
if (isPrime(test_num1)) {
printf("%d 是素数,\n", test_num1);
} else {
printf("%d 不是素数,\n", test_num1);
}
if (isPrime(test_num2)) {
printf("%d 是素数,\n", test_num2);
} else {
printf("%d 不是素数,\n", test_num2);
}
return 0;
}
// 函数定义
// 判断一个数是否为素数
bool isPrime(int num) {
if (num <= 1) {
return false; // 1和小于1的数都不是素数
}
// 从2开始,到 num/2 为止,检查是否有能整除 num 的数
// 优化:只需检查到 sqrt(num)
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) {
return false; // 如果能被整除,则不是素数
}
}
return true; // 如果都不能被整除,则是素数
}
核心知识点:
- 函数声明:
bool isPrime(int num);告诉编译器存在一个名为isPrime的函数,它接受一个int参数,返回一个bool值,这必须在调用之前完成。 - 函数定义: 包含函数的具体实现。
- 参数传递: C语言中,函数参数传递是“值传递”,这意味着调用函数时,会将实参的值复制一份给形参,在函数内部修改形参不会影响外部的实参。
- 返回值: 使用
return语句从函数返回一个值,函数的返回类型必须与return语句值的类型匹配。 stdbool.h: C99标准引入的头文件,可以使用bool,true,false来进行布尔逻辑运算,使代码更易读。
习题2:递归函数
**
使用递归函数计算斐波那契数列的第n项。(斐波那契数列:1, 1, 2, 3, 5, 8, 13...,即 F(n) = F(n-1) + F(n-2))
答案与解析:
#include <stdio.h>
// 函数声明
long long fibonacci(int n);
int main() {
int n = 10;
printf("斐波那契数列的第 %d 项是: %lld\n", n, fibonacci(n));
return 0;
}
// 递归函数定义
long long fibonacci(int n) {
// 基准情况(递归出口)
if (n == 1 || n == 2) {
return 1;
}
// 递归情况
else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
核心知识点:
- 递归: 一个函数在其内部直接或间接地调用自身的编程技巧。
- 递归的两个要素:
- 基准情况(Base Case): 递归必须有一个或多个出口,用于终止递归,否则会导致无限递归,最终栈溢出,本例中就是
n == 1 || n == 2。 - 递归情况(Recursive Case): 函数调用自身,但处理的参数向基准情况靠近,本例中就是
fibonacci(n - 1) + fibonacci(n - 2)。
- 基准情况(Base Case): 递归必须有一个或多个出口,用于终止递归,否则会导致无限递归,最终栈溢出,本例中就是
- 效率问题: 这种简单的递归解法虽然直观,但效率极低,因为存在大量的重复计算,在实际应用中,通常会使用动态规划或迭代法来优化。
第六章:指针
本章目标: 理解指针的概念,掌握指针的定义、初始化、指针运算(解引用、取地址)、指针与数组的关系。
习题1:指针基础
**
定义一个整型变量 a 和一个指向整型的指针 p,通过指针 p 修改 a 的值,并输出修改前后的 a 和 p 的值(以及 p 所指向的地址)。
答案与解析:
#include <stdio.h>
int main() {
int a = 10;
int *p; // 定义一个指向整型的指针 p
// 将指针 p 指向变量 a
// & 是取地址运算符
p = &a;
printf("初始值:\n");
printf(" a 的值: %d\n", a);
printf(" p 的值 (a的地址): %p\n", (void*)p); // %p 输出地址,(void*)是类型转换,更安全
printf(" p 指向的值 (*p): %d\n", *p); // * 是解引用/间接寻址运算符
// 通过指针 p 修改 a 的值
*p = 20;
printf("\n通过指针 p 修改后:\n");
printf(" a 的值: %d\n", a);
printf(" p 的值 (a的地址): %p\n", (void*)p);
printf(" p 指向的值 (*p): %d\n", *p);
return 0;
}
核心知识点:
- 指针变量: 存储内存地址的变量。
&运算符: 取地址运算符。&a得到变量a在内存中的地址。- *`` 运算符:**
- 在定义时(如
int *p;),表示p是一个指针。 - 在使用时(如
*p),表示解引用,即访问p所指向地址中存储的值。
- 在定义时(如
- 指针与变量的关系: 指针
p存储的是变量a的地址,*p和a访问的是同一块内存空间。
习题2:指针与数组
** 使用指针遍历数组,并打印数组中每个元素的值和地址。
答案与解析:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p; // 定义一个整型指针
int i;
// 方法一:让指针指向数组首元素
// 数组名 arr 会“衰变”为其首元素的地址
p = arr;
printf("使用指针遍历数组:\n");
for (i = 0; i < 5; i++) {
// *(p + i) 是访问 p+i 地址处的值
// p + i 是指针的算术运算,它跳过 i 个 sizeof(int) 字节
printf("arr[%d] 的值: %d, 地址: %p\n", i, *(p + i), (void*)(p + i));
}
printf("\n使用数组下标和指针算术运算的等价关系:\n");
printf("arr[2] 等价于 *(arr + 2) 等价于 p[2]\n");
printf("arr[2] 的值: %d\n", arr[2]);
printf("*(arr + 2) 的值: %d\n", *(arr + 2));
printf("p[2] 的值: %d\n", p[2]);
return 0;
}
核心知识点:
- 数组名与指针: 在大多数情况下,数组名
arr会衰变成指向其第一个元素的指针。p = arr;和p = &arr[0];是等价的。 - 指针算术运算: 对指针进行 或 运算,移动的步长是其指向数据类型的
sizeof字节数。p + 1会移动sizeof(int)个字节。 - *
arr[i]与 `(arr + i)` 的等价性:** 这是C语言中一个非常重要的概念,数组下标访问和指针算术访问在底层是等价的。
第七章:结构体与共用体
本章目标: 掌握结构体的定义、初始化和成员访问,理解其与数组的区别。
习题1:结构体定义与使用
**
定义一个表示学生的结构体 Student,包含学号(id)、姓名(name)和成绩(score),然后创建一个 Student 类型的变量,并为其赋值,最后输出这些信息。
答案与解析:
#include <stdio.h>
#include <string.h> // 用于 strcpy 函数
// 定义一个名为 Student 的结构体类型
struct Student {
int id; // 学号
char name[50]; // 姓名
float score; // 成绩
};
int main() {
// 声明一个 Student 类型的变量 stu
struct Student stu;
// 为结构体成员赋值
stu.id = 1001;
// strcpy 用于复制字符串,不能直接用 =
strcpy(stu.name, "张三");
stu.score = 95.5f;
// 输出结构体成员的值
printf("学生信息:\n");
printf(" 学号: %d\n", stu.id);
printf(" 姓名: %s\n", stu.name);
printf(" 成绩: %.1f\n", stu.score); // .1 表示输出1位小数
return 0;
}
核心知识点:
- 结构体定义:
struct Student { ... };定义了一个新的数据类型Student。 - 结构体变量声明:
struct Student stu;声明了一个Student类型的变量。 - 成员访问: 使用 成员访问运算符来访问和修改结构体变量的成员,如
stu.id。 - 字符串赋值: 不能用
stu.name = "张三";来赋值字符串,因为name是一个字符数组,应使用strcpy函数。
第八章:文件操作
本章目标: 掌握文件的打开、关闭、读写等基本操作。
习题1:文件写入与读取
- 以写入模式创建一个名为
data.txt的文件。 - 向文件中写入两行文本:"Hello, C Language!" 和 "This is a file test."。
- 关闭文件。
- 以读取模式重新打开
data.txt文件。 - 逐行读取文件内容并打印到屏幕上,直到文件末尾。
- 关闭文件。
答案与解析:
#include <stdio.h>
#include <string.h>
int main() {
FILE *fp; // 定义一个文件指针
char buffer[256]; // 用于读取文件的缓冲区
// --- 1. 写入文件 ---
// "w" 表示写入模式,如果文件不存在则创建,如果存在则清空
fp = fopen("data.txt", "w");
if (fp == NULL) { // 检查文件是否成功打开
perror("打开文件失败");
return 1;
}
fprintf(fp, "Hello, C Language!\n"); // fprintf 向文件写入格式化字符串
fprintf(fp, "This is a file test.\n");
fclose(fp); // 关闭文件
printf("文件写入成功,\n");
// --- 2. 读取文件 ---
// "r" 表示读取模式
fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
printf("\n从文件中读取内容:\n");
// fgets 从文件中读取一行
// 如果读取成功,返回 buffer;如果到达文件末尾或出错,返回 NULL
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
// printf 输出从文件中读取的字符串
// buffer 中可能包含换行符 \n,所以不需要再加 \n
printf("%s", buffer);
}
fclose(fp); // 关闭文件
printf("\n文件读取完毕,\n");
return 0;
}
核心知识点:
FILE指针: 所有文件操作都通过FILE指针来进行。fopen函数:FILE *fopen(const char *filename, const char *mode);打开文件。"w":写入,覆盖原有内容。"r":读取,文件必须存在。"a":追加,在文件末尾写入。"r+":读写,文件必须存在。
fclose函数:int fclose(FILE *stream);关闭文件,释放资源。fprintf函数: 向文件写入格式化数据,类似于printf,但第一个参数是FILE指针。fgets函数:char *fgets(char *str, int n, FILE *stream);从文件中读取一行,最多读取n-1个字符。- 错误检查: 每次调用
fopen后,都应该检查返回的FILE指针是否为NULL,以判断文件是否成功打开。perror函数可以打印出系统错误信息。
学习建议
- 动手敲代码: 不要只看不练,把上面的代码都亲手敲一遍,修改参数,观察结果变化。
- 理解原理: 每道题后面都有“核心知识点”,务必理解这些概念,而不仅仅是记住代码。
- 调试: 学会使用调试器(如 VS Code 的调试插件、GDB 等)单步执行代码,观察变量的变化,这比只看输出结果更有帮助。
- 多做练习: 这些是经典题库,多做类似题目,举一反三,才能真正掌握。
希望这份详尽的指南对您的C语言学习有所帮助!祝您学习顺利!
