大一新生挂科后靠这套题库逆袭
先说个真事儿。我有个学弟,大一下学期C语言挂了两科,整个人都快崩了。他找到我,我说:”别慌,题库走起。”三个月后,他拿着满分的期末试卷来找我,说”哥,这题我原题见过”。
所以今天这篇文章,就是要让你从”挂科边缘”变成”卷王本王”。全是干货,建议收藏反复看。
一、为什么C语言这么难挂?
先别急着刷题,得明白为什么C语言会挂。不是老师出题难,是你根本不知道考点在哪。
最常见的挂科点,90%的人都会踩坑:
- 指针永远搞不明白 — 这是C语言最大的拦路虎
- 数组和字符串边界不清 — 越界是家常便饭
- 函数传参机制不懂 — 值传递和地址传递傻傻分不清楚
- 结构体操作混乱 — 特别是结构体指针
- 文件操作根本不会 — 期末必考,但平时从来不练
别慌,下面我按题型分类,把C语言期末考试能考的所有重点都给你整理出来。每一道题都有详细解析,不是那种只给答案不给过程的敷衍内容。
二、选择题专项 — 基础概念必考点
第1题:数据类型大小
下面哪个数据类型在32位系统中占用4个字节?
A) int B) char C) short D) float
答案:A、D
int和float都是4个字节。char是1个字节,short是2个字节。这个知识点一定要记住,因为很多题目会考你sizeof运算符的结果。
延伸记忆表:
| 类型 | 字节数(32位) | 字节数(64位) |
|---|---|---|
| char | 1 | 1 |
| short | 2 | 2 |
| int | 4 | 4 |
| long | 4 | 8 |
| float | 4 | 4 |
| double | 8 | 8 |
| pointer | 4 | 8 |
第2题:运算符优先级
表达式 (int)(a+b)/2 的运算顺序是?
A) a+b → (int) → /2
B) (int)(a+b) → /2
C) a+b → /2 → (int)
D) 以上都不对
答案:B
强制类型转换的优先级高于除法。所以先算(int)(a+b),然后再除以2。
运算符优先级口诀(背下来):
括号函数下标先,单目运算紧随前。算术加减比位大,逻辑与或非最后。
第3题:变量作用域
下面程序的输出是?
#include <stdio.h>
int a = 10;
void func() {
int a = 20;
printf("%d ", a);
}
int main() {
func();
printf("%d", a);
return 0;
}
A) 20 10 B) 10 20 C) 20 20 D) 10 10
答案:A
函数内部有局部变量a,优先使用局部的20。main函数里用的是全局变量a,所以输出10。
记住:局部变量优先于全局变量
第4题:static关键字
下面程序的输出是?
#include <stdio.h>
void count() {
static int n = 0;
n++;
printf("%d ", n);
}
int main() {
count();
count();
count();
return 0;
}
A) 1 2 3 B) 1 1 1 C) 0 1 2 D) 编译错误
答案:A
static变量只初始化一次,每次调用都会保留上次修改后的值。所以第一次调用n变成1,第二次变成2,第三次变成3。
static的本质:延长变量的生命周期
三、程序阅读题 — 指针专项(重点!重点!重点!)
指针是C语言的灵魂,也是挂科的重灾区。来,直接上真题。
第5题:指针基础
下面程序的输出是?
#include <stdio.h>
int main() {
int a = 100, b = 200;
int *p = &a;
*p = 300;
printf("a=%d, b=%d\n", a, b);
return 0;
}
A) a=100, b=200
B) a=300, b=200
C) a=300, b=300
D) a=100, b=300
答案:B
p指向a,*p=300就是给a赋值300。b完全没有被修改。
图解:
p → [a:300] b:[200]
第6题:指针与数组
下面程序的输出是?
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d, ", *p++);
printf("%d, ", *p);
printf("%d\n", *(p+2));
return 0;
}
A) 10, 20, 40
B) 10, 20, 30
C) 20, 30, 40
D) 10, 10, 30
答案:A
- p++:先输出* p(10),然后p自增指向20
- * p:输出20
- *(p+2):从20的位置往后跳2个,是40
关键:++的优先级高于*,但这里是后置++,先取值再自增
第7题:二级指针(期末必考!)
下面程序的输出是?
#include <stdio.h>
int main() {
int a = 10, b = 20;
int *p = &a;
int **pp = &p;
**pp = 30;
printf("%d, %d\n", a, b);
return 0;
}
A) 10, 20 B) 30, 20 C) 10, 30 D) 30, 30
答案:B
pp是指向p的指针,*pp就是p,pp就是a。所以pp=30就是给a赋值30。
二级指针图解:
pp → p → [a:30] [b:20]
第8题:指针与函数(值传递陷阱)
下面程序的输出是?
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap(&x, &y);
printf("%d, %d\n", x, y);
return 0;
}
A) 10, 20 B) 20, 10 C) 10, 10 D) 20, 20
答案:B
通过指针传递地址,可以修改实参的值。这是交换函数必须用指针的原因。
如果不用指针会怎样?
void swap_bad(int a, int b) { // 值传递,修改的是副本
int temp = a;
a = b;
b = temp;
}
这个版本完全没用,main里的x和y不会变。
第9题:指针与字符串
下面程序的输出是?
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello";
char str2[] = "World";
char *p1 = str1;
char *p2 = str2;
printf("%d, ", strlen(p1));
printf("%d\n", p2 - p1);
return 0;
}
A) 5, 0 B) 5, -5 C) 5, 10 D) 编译错误
答案:A
strlen(p1)返回字符串长度5。p2-p1是地址相减,但因为两个指针不指向同一个数组,结果未定义(通常是0或者负数)。
注意:不同数组的指针相减是未定义行为!
第10题:指针数组 vs 数组指针
下面哪个是数组指针?
A) int *p[5]
B) int (*p)[5]
C) int p[5]
D) int *p
答案:B
int *p[5]— 指针数组,每个元素是指针int (*p)[5]— 数组指针,指向有5个元素的数组
记忆技巧:括号优先
int (*p)[5] — p先和*结合,说明p是指针,指向数组
int *p[5] — p先和[5]结合,说明p是数组,元素是指针
四、程序阅读题 — 结构体专项
第11题:结构体基础
下面程序的输出是?
#include <stdio.h>
struct Student {
int id;
char name[20];
float score;
};
int main() {
struct Student s = {1, "Alice", 95.5};
printf("%d, %s, %.1f\n", s.id, s.name, s.score);
return 0;
}
A) 1, Alice, 95.5
B) 1, 95.5, Alice
C) 编译错误
D) 运行错误
答案:A
结构体初始化按声明顺序赋值。
第12题:结构体指针
下面程序的输出是?
#include <stdio.h>
struct Student {
int id;
float score;
};
int main() {
struct Student s = {1, 95.5};
struct Student *p = &s;
printf("%d, %.1f\n", p->id, p->score);
return 0;
}
A) 1, 95.5
B) 编译错误
C) 运行错误
D) 以上都不对
答案:A
结构体指针用->访问成员,等价于(*p).id。
第13题:结构体数组排序
下面程序的输出是?
#include <stdio.h>
#include <string.h>
struct Student {
char name[20];
int score;
};
int main() {
struct Student s[3] = {
{"Bob", 85},
{"Alice", 92},
{"Charlie", 78}
};
// 按成绩排序(冒泡)
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2-i; j++)
if (s[j].score < s[j+1].score) {
struct Student temp = s[j];
s[j] = s[j+1];
s[j+1] = temp;
}
printf("%s, ", s[0].name);
printf("%s, ", s[1].name);
printf("%s\n", s[2].name);
return 0;
}
A) Alice, Bob, Charlie
B) Bob, Alice, Charlie
C) Charlie, Bob, Alice
D) Alice, Charlie, Bob
答案:A
按成绩从高到低排序:Alice(92) > Bob(85) > Charlie(78)
五、程序阅读题 — 文件操作专项
第14题:文件写入
下面程序执行后,文件test.txt的内容是?
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("文件打开失败\n");
return 1;
}
fprintf(fp, "Hello\n");
fprintf(fp, "World\n");
fclose(fp);
printf("文件写入成功\n");
return 0;
}
A) Hello
B) World
C) Hello
World
D) 文件不存在
答案:C
w模式是写模式,会覆盖原文件内容。两次fprintf分别写入两行。
第15题:文件读取
下面程序的输出是?
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "r");
char ch;
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
fclose(fp);
return 0;
}
A) 读取文件所有字符并输出
B) 只读取第一个字符
C) 编译错误
D) 运行错误
答案:A
fgetc逐个读取字符,直到遇到EOF为止。
第16题:文件操作陷阱
下面程序的输出是?
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "r");
// 忘记判断fp是否为NULL
char ch = fgetc(fp);
printf("%c\n", ch);
fclose(fp);
return 0;
}
A) 正常输出
B) 编译错误
C) 如果文件不存在,运行时崩溃
D) 输出EOF
答案:C
忘记判断文件是否打开成功,如果文件不存在,fp为NULL,对NULL解引用会崩溃。
正确写法:
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("文件打开失败\n");
return 1;
}
// 正常操作...
六、编程题专项 — 必考题型
第17题:求阶乘
编写函数求n的阶乘。
答案:
#include <stdio.h>
long factorial(int n) {
if (n < 0) return -1; // 错误处理
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);
}
int main() {
int n = 5;
printf("%d! = %ld\n", n, factorial(n));
return 0;
}
运行结果: 5! = 120
要点:
- 递归求阶乘
- 注意边界条件n=0和n=1
- 返回值类型用long防止溢出
第18题:冒泡排序
编写冒泡排序函数,对数组进行升序排列。
答案:
#include <stdio.h>
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
int swapped = 0; // 优化:如果没有交换说明已经有序
for (int j = 0; j < n-1-i; j++) {
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
swapped = 1;
}
}
if (swapped == 0) break;
}
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n);
printf("排序后:");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
运行结果: 排序后:11 12 22 25 34 64 90
要点:
- 外层循环控制轮数
- 内层循环进行相邻比较
- 优化:加入swapped标志,提前结束
第19题:字符串反转
编写函数实现字符串原地反转。
答案:
#include <stdio.h>
#include <string.h>
void reverseString(char str[]) {
int len = strlen(str);
for (int i = 0; i < len / 2; i++) {
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}
int main() {
char str[] = "Hello World";
reverseString(str);
printf("反转后:%s\n", str);
return 0;
}
运行结果: 反转后:dlroW olleH
要点:
- 只需要遍历到len/2
- 首尾交换,而不是新建数组
第20题:求最大公约数
编写函数求两个数的最大公约数(GCD)。
答案:
#include <stdio.h>
int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
int main() {
int m = 48, n = 18;
printf("gcd(%d, %d) = %d\n", m, n, gcd(m, n));
return 0;
}
运行结果: gcd(48, 18) = 6
要点:
- 辗转相除法(欧几里得算法)
- 核心:gcd(a,b) = gcd(b, a%b)
第21题:素数判断
编写函数判断一个数是否为素数。
答案:
#include <stdio.h>
#include <math.h>
int isPrime(int n) {
if (n <= 1) return 0;
if (n == 2) return 1;
if (n % 2 == 0) return 0;
for (int i = 3; i <= sqrt(n); i += 2) {
if (n % i == 0) return 0;
}
return 1;
}
int main() {
for (int i = 1; i <= 20; i++) {
if (isPrime(i))
printf("%d ", i);
}
printf("\n");
return 0;
}
运行结果: 2 3 5 7 11 13 17 19
要点:
- 只需要判断到sqrt(n)
- 可以先排除偶数,然后只检查奇数
第22题:二分查找
编写二分查找函数,在有序数组中查找目标值。
答案:
#include <stdio.h>
int binarySearch(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1; // 未找到
}
int main() {
int arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 23;
int result = binarySearch(arr, n, target);
if (result != -1)
printf("元素在索引 %d 处\n", result);
else
printf("元素未找到\n");
return 0;
}
运行结果: 元素在索引 5 处
要点:
- 数组必须有序
- mid计算要防止溢出
- 时间复杂度O(log n)
第23题:链表基本操作
实现链表的创建、插入和打印。
答案:
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点
struct Node {
int data;
struct Node *next;
};
// 创建新节点
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表尾部插入
void insertTail(struct Node** head, int data) {
struct Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
struct Node* temp = *head;
while (temp->next != NULL)
temp = temp->next;
temp->next = newNode;
}
// 打印链表
void printList(struct Node* head) {
struct Node* temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
int main() {
struct Node* head = NULL;
insertTail(&head, 10);
insertTail(&head, 20);
insertTail(&head, 30);
printList(head);
return 0;
}
运行结果: 10 -> 20 -> 30 -> NULL
要点:
- 理解二级指针的使用
- 内存管理要用malloc/free
第24题:动态内存分配
编写函数动态创建二维数组并赋值打印。
答案:
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// 动态分配二维数组
int **arr = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++)
arr[i] = (int*)malloc(cols * sizeof(int));
// 赋值
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
arr[i][j] = i * cols + j + 1;
// 打印
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++)
printf("%3d", arr[i][j]);
printf("\n");
}
// 释放内存
for (int i = 0; i < rows; i++)
free(arr[i]);
free(arr);
return 0;
}
运行结果:
1 2 3 4
5 6 7 8
9 10 11 12
要点:
- 先分配行指针数组,再分配每行的列数组
- 记得释放内存,避免内存泄漏
七、综合题 — 模拟考场真题
第25题:学生成绩管理(综合应用)
编写程序实现学生成绩管理:输入学生信息,计算平均分,按成绩排序输出。
答案:
#include <stdio.h>
#include <string.h>
#define MAX_STUDENTS 100
struct Student {
int id;
char name[20];
float score[3]; // 三门成绩
float average;
};
// 输入学生信息
void inputStudent(struct Student *s, int index) {
printf("输入第%d个学生信息:\n", index + 1);
printf("学号:");
scanf("%d", &s->id);
printf("姓名:");
scanf("%s", s->name);
printf("三门成绩:");
s->average = 0;
for (int i = 0; i < 3; i++) {
scanf("%f", &s->score[i]);
s->average += s->score[i];
}
s->average /= 3;
}
// 按平均分排序(冒泡)
void sortStudents(struct Student students[], int n) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - 1 - i; j++)
if (students[j].average < students[j+1].average) {
struct Student temp = students[j];
students[j] = students[j+1];
students[j+1] = temp;
}
}
// 打印成绩单
void printReport(struct Student students[], int n) {
printf("\n=== 成绩单 ===\n");
printf("%-6s %-10s %-8s %-8s %-8s %-8s\n", "学号", "姓名", "成绩1", "成绩2", "成绩3", "平均分");
printf("------------------------------------------------------------\n");
for (int i = 0; i < n; i++) {
printf("%-6d %-10s", students[i].id, students[i].name);
for (int j = 0; j < 3; j++)
printf("%-8.1f", students[i].score[j]);
printf("%-8.1f\n", students[i].average);
}
}
int main() {
struct Student students[MAX_STUDENTS];
int n;
printf("请输入学生人数:");
scanf("%d", &n);
for (int i = 0; i < n; i++)
inputStudent(&students[i], i);
sortStudents(students, n);
printReport(students, n);
return 0;
}
第26题:字符串加密(经典题)
实现凯撒密码:将每个字母向后移动3位(A→D,B→E...)。
答案:
#include <stdio.h>
#include <string.h>
void caesarCipher(char str[], int shift) {
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] >= 'a' && str[i] <= 'z')
str[i] = (str[i] - 'a' + shift) % 26 + 'a';
else if (str[i] >= 'A' && str[i] <= 'Z')
str[i] = (str[i] - 'A' + shift) % 26 + 'A';
}
}
int main() {
char message[] = "Hello World";
printf("原文:%s\n", message);
caesarCipher(message, 3);
printf("加密:%s\n", message);
caesarCipher(message, -3); // 解密
printf("解密:%s\n", message);
return 0;
}
运行结果:
原文:Hello World
加密:Khoor Zruog
解密:Hello World
第27题:进制转换
将十进制数转换为任意进制(2-16进制)。
答案:
#include <stdio.h>
void convertBase(int n, int base) {
char result[100];
int i = 0;
if (n == 0) {
printf("0\n");
return;
}
while (n > 0) {
int remainder = n % base;
if (remainder < 10)
result[i++] = remainder + '0';
else
result[i++] = remainder - 10 + 'A';
n /= base;
}
// 逆序输出
for (int j = i - 1; j >= 0; j--)
printf("%c", result[j]);
printf("\n");
}
int main() {
int n = 255;
printf("十进制:%d\n", n);
printf("二进制:%d\n", 0); // 调用convertBase(n, 2);
printf("八进制:%d\n", 0); // 调用convertBase(n, 8);
printf("十六进制:%d\n", 0); // 调用convertBase(n, 16);
// 实际调用
printf("二进制:");
convertBase(n, 2);
printf("八进制:");
convertBase(n, 8);
printf("十六进制:");
convertBase(n, 16);
return 0;
}
运行结果:
二进制:11111111
八进制:377
十六进制:FF
八、常见错误总结 — 考前必看
1. 指针错误(最常见!)
// ❌ 错误:野指针
int *p;
*p = 10; // p没有指向任何有效地址
// ✅ 正确:初始化指针
int *p = NULL;
int a = 10;
p = &a;
*p = 10;
2. 数组越界
// ❌ 错误:越界访问
int arr[5] = {1, 2, 3, 4, 5};
printf("%d", arr[5]); // 越界!
// ✅ 正确:注意数组下标从0开始
int arr[5];
for (int i = 0; i < 5; i++) // i < 5,不是 <= 5
scanf("%d", &arr[i]);
3. 字符串忘记结束符
// ❌ 错误:忘记留空间给\0
char str[5] = "Hello"; // "Hello"需要6个字节(含\0)
// ✅ 正确:多留一个字节
char str[6] = "Hello";
// 或者
char str[] = "Hello"; // 自动计算长度
4. 文件操作忘记关闭
// ❌ 错误:忘记关闭文件
FILE *fp = fopen("test.txt", "r");
fscanf(fp, "%s", buf);
// 没有fclose
// ✅ 正确:用完一定要关闭
FILE *fp = fopen("test.txt", "r");
if (fp != NULL) {
fscanf(fp, "%s", buf);
fclose(fp);
}
九、考前冲刺建议
1. 重点复习这些章节
- 指针(必考,占比30%以上)
- 结构体(必考,占比20%)
- 文件操作(必考,占比10%)
- 字符串处理(占比15%)
- 函数(基础,占比15%)
- 数组(基础,占比10%)
2. 每天练什么
| 天数 | 内容 |
|---|---|
| 第1天 | 选择题30道,重点指针和结构体 |
| 第2天 | 程序阅读题20道,重点指针和函数 |
| 第3天 | 编程题10道,练熟冒泡排序、二分查找 |
| 第4天 | 综合题5道,模拟真实考试 |
| 第5天 | 错题回顾,查漏补缺 |
3. 考场技巧
- 选择题:先做有把握的,指针题一定要画图分析
- 程序阅读题:一步一步跟踪变量,不要跳步
- 编程题:先写伪代码,再写具体实现,注意边界条件
十、最后的话
C语言挂科不可怕,可怕的是不知道错在哪、不知道怎么补。
这套题库覆盖了你期末考试90%的考点。每天练一道,一周后你的水平能提升一个档次。
记住:指针是C语言的灵魂,结构体是C语言的核心。把这两块吃透,C语言考试基本稳了。
还有什么问题?评论区留言,我看到都会回。
祝大家都拿高分!💪
提示:这篇文章建议收藏,考前反复看。遇到不懂的指针题,一定要画图理解,不要死记硬背。
