大一新生挂科后靠这套题库逆袭


先说个真事儿。我有个学弟,大一下学期C语言挂了两科,整个人都快崩了。他找到我,我说:”别慌,题库走起。”三个月后,他拿着满分的期末试卷来找我,说”哥,这题我原题见过”。

所以今天这篇文章,就是要让你从”挂科边缘”变成”卷王本王”。全是干货,建议收藏反复看。


一、为什么C语言这么难挂?

先别急着刷题,得明白为什么C语言会挂。不是老师出题难,是你根本不知道考点在哪。

最常见的挂科点,90%的人都会踩坑:

  1. 指针永远搞不明白 — 这是C语言最大的拦路虎
  2. 数组和字符串边界不清 — 越界是家常便饭
  3. 函数传参机制不懂 — 值传递和地址传递傻傻分不清楚
  4. 结构体操作混乱 — 特别是结构体指针
  5. 文件操作根本不会 — 期末必考,但平时从来不练

别慌,下面我按题型分类,把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语言考试基本稳了。

还有什么问题?评论区留言,我看到都会回。

祝大家都拿高分!💪


提示:这篇文章建议收藏,考前反复看。遇到不懂的指针题,一定要画图理解,不要死记硬背。